Ojective-C Programming – Copying Object

นี่ก็น่าจะเป็นเรื่อง objective-c แบบเพียวๆเกือบๆจะสุดท้ายละ ก็เหลืออีกไม่กี่เรื่องที่ผมคิดว่าน่าจะเพียงพอสำหรับการเริ่มต้นกับ cocoa เอาละเข้าเรื่องเลยดีกว่า

โดยปกติเราเวลาที่เราประกาศตัวแปรขึ้นมาสักตัว ถ้าไม่ใช่ class ก็จะเป็น primitive type เป็นต้นว่า int , char , double การที่เราจะ copy ข้อมูลจาก ตัวแปรหนึ่งไปยังอีกตัวแปร หนึ่งนั้นทำได้ง่ายมาก เพียงแค่ใช้เครื่องหมาย = แค่นี้เองเช่นเป็นต้นว่า

จาก code เบื้องต้นจะเห็นว่า

  • บรรทัดที่ 3 เราได้ให้ b มีค่าเท่ากับ a
  • บรรทัดที่ 4 เพิ่มค่า a ด้วย 15
  • บรรทัดที่ 5 เพิ่มค่า b ด้วย 10
  • แน่นอนว่า จาก code ข้างบนนี้ ค่า a กับ b มีค่าต่างกันคือ 20 กับ 25
  • สรุปง่ายๆว่า เราได้ให้ค่า b เท่ากับ ค่า a ในบรรทัดที่ 3 แต่เมื่อเราเปลี่ยนค่า a ในบรรทัดที่ 4 ก็ไม่ได้มีผลกระทบอะไรกับค่า b
  • และในทำนองเดียวกัน ถ้าเราทำการแก้ไขค่าของ a ก็ไม่ได้มีผลอะไรกับ b

แต่การประกาศ class instance นั้นถ้าเราเขียนโปรแกรมในลักษณะเหมือนๆกับ ข้างบน ดังเช่นตัวอย่างข้างล่าง

จาก code ข้างบน

  • เราได้ประกาศ instance ของ NSMutableString มา 2 ตัวคือ hello กับ temp
  • ในบรรทัดที่ 8 และเราก็ได้ให้ ค่า temp มีค่าเท่ากับ hello นั่นก็แปลว่า temp ควรจะมีค่า เป็น Hello
  • จากนั้นในบรรทัดที่ 9 เราก็ได้เปลี่ยนแปลงค่า ของ temp โดยการต่อท้ายด้วย World
  • และบรรทัดที่ 10 เราก็ให้พิมพ์ค่าตัวแปร hello ออกมา
  • แต่ผลปรากฎว่า มันกลับพิมพ์คำว่า Hello World ออกมา ทั้งๆที่เราก็ไม่ได้ไปทำการแก้ไขค่าใน ตัวแปร hello เลยสักนิด

ทำไมถึงเป็นแบบนี้ ?

เพราะว่า การประกาศ class instance นั้นเป็นการประกาศ pointer ( คือตัวแปรที่เก็บค่าของตำแหน่ง memory ) ของ class นั้นๆ ฉนั้นการที่เขียน code ว่า

temp = helloString

ก็เป็นการบอกว่า ให้ temp นั้นชี้ไปยังตำแหน่ง memory เดียวกันกับ hello ฉนั้นแล้วเมื่อมันเป็น ตำแหน่ง memory เดียวกัน ถ้าหากเราทำการแก้ไข temp จึงทำให้ hello ต้องเปลี่ยนไปด้วย

เหมือนในภาษา c/c++ถ้าดู code ต่อไปนี้

การที่เราเขียน code แบบนี้เป็นการให้ temp ชี้ไปที่ตำแหน่ง memory ของ hello ไม่ได้เป็นการ copy ค่า ( เราต้องการให้ temp มีค่าเป็นคำว่า Hello World!! ) ฉนั้นแล้วเมื่อเราสั่ง printf ตัวแปร temp ออกมาก็จะไม่ได้คำว่า Hello World!! เพราะเป็นการชี้ตำแหน่ง memory ไปยังตำแหน่งใหม่ ถ้าหากเราจะ ให้ค่า temp เป็น Hello World เหมือนกับ hello เราต้องใช้ ฟังชั่นที่ชื่อว่า strcpy ทำการ copy string ถ้าเป็นภาษา c/c++ อาจจะเขียนได้แบบนี้

ปล. ใครที่ยังไม่เข้าใจเรื่อง pointer ให้ไปอ่านเพิ่มเติมน่ะครับ เดี๋ยวจะงง
http://www.vcharkarn.com/vlesson/showlesson.php?lessonid=1&pageid=7

แล้วเราจะ copy มันได้ยังไง ?

ใน Foundation class นั้นจะมี method ที่ชื่อว่า copy และ mutableCopy เพื่อให้เราสามารถ clone ค่าจาก instance หนึ่งไปยังอีก instance หนึ่งได้ และสอง method นี้ต่างกันกันตรงที่ว่า ถ้าเป็นหากเราต้องการจะคัดลอกไปแล้วให้มันแก้ไขได้ ก็ต้องใช้ mutableCopy ( สำหรับ mutable object นั้นก็ควรจะใช้ mutableCopy )

มาดูกันว่าจากข้างบนเราจะเปลี่ยนให้ temp มันเท่ากับ helloString ได้ยังไง

ถ้าเรา compile และ run ดูจะเห็นว่าผลลัพธ์ที่ได้จะออกมาเป็น

Hello
HelloWorld

ซึ่งจะต่างจากโปรแกรมแรกที่เราเขียน เพราะว่า เราทำการ copy ค่าจาก helloSting มาให้ยัง temp ( การ copy ในที่นี้หมายถึงการคัดลอกข้อมูลในตำแหน่ง memory ที่ต้องการไปยังอีกที่) นั่นก็แปลว่าค่าของตัวแปรทั้งสองอันนี้แยกจากกัน ฉนั้นถ้าเราแก้ไขตัวแปร temp ก็ไม่ได้เกี่ยวอะไรกับ helloString

Shallow – Deep

อย่างที่บอกไปแล้วว่า instance นั้นคือ pointer ดีๆนี่เอง ลองพิจารณา code ข้างล่างนี้

จากโปรแกรมข้างบน

    เราเริ่มด้วยการประกาศตัวแปรมาทั้ง 3 ตัวคือ arrayOne arrayTwo และ helloString

  • หลังจากนั้น เราก็ได้เอาตัวแปร helloString ไปเก็บไว้ที่ arrayOne
  • จากนั้นก็ arrayTwo ก็ได้ mutableCopy จาก arrayOne
  • สรุปว่า ตอนนี้ arrayOne มี member 1 ตัวคือ helloString ส่วน arrayTwo ก็มี member 1 ตัวเหมือนกันเพราะว่าไปก๊อปจาก arrayOne มา
  • หลังจากนั้นเราได้ทำการแก้ไข ค่าของ member ใน arrayTwo โดยเพิ่มคำว่า World เข้าไป
  • สุดท้ายเราพิมพ์ค่าของ arrayOne ออกมาร แต่ผลที่ได้คือ มันพิมพ์คำว่า Hello World !!!!
  • อ้าวทำไมมันเป็นแบบนี้ละ ทั้งๆที่เราแก้ไขตัวแปร ใน arrayTwo แล้วมันเกี่ยวอะไรกับ arrayOne ?

    จริงอยู่ครับว่า arrayTwo นั้นก๊อปปี้ arrayOne มา นั่นก็แปรได้ว่ามันใช้ memory คนละส่วนกันแล้วดังนั้นมันก็ควรจะไม่เกี่ยวกัน ก็ถูกต้องแล้วละครับ ว่ามันไม่ได้เกียวกันการแก้ไข member ใน arrayOne ก็ไม่ได้เกียวกับ arrayTwo

    แต่เผอิญว่า ตัวแปร ที่ arrayOne นั้นเก็บไว้มันเป็น pointer ที่ชี้ไปยัง helloString นั่นก็แปลได้ง่ายๆว่า member ใน array นั้นไม่ได้ถูก clone มาด้วย เป็นแค่การ copy reference มาแค่นั้น ฉนั้นการแก้ไข ตัวแปรใน arrayOne ย่อมมีผลกระทบต่อ arrayTwo สิ่งที่มันเป็นลักษณะ นี้เรียกว่า shallow copy

    แล้วจะทำยังไงให้มันแยกขาดจากกันเลย ?

    คำตอบคือ เราต้อง ทำเองครับ เพราะว่ามันไม่ได้ทำให้เรา แต่จะทำยังไงละ ?

    Copying Class Instance

    จริงอยู่ว่าเราสามารถใช้ copy , mutableCopy ได้แต่ถ้าหากเป็น class ที่เราเขียนขึ้นมาเองนั้นไม่สามารถทำได้ เราต้องเขียนเองเหมือนกัน

    สรุปว่าตอนนี้ก็มีตัวอย่างที่เราต้องทำเอง ถึง 2 เคสด้วยกัน วิธีการก็ไม่ได้ยากเย็นอะไรนัก เราเพียงแค่ implement interface <NSCopy> หรือ <NSMutableCopy> เท่านั้นเอง มาดูกันเลยดีกว่า ว่ามันหน้าตาเป็นยังไง

    ก็ดูจาก code ข้างบนจะเห็นว่า ตอนที่ประกาศ class จะมีส่วนเพิ่มเติมเข้ามาคือ <NSCopying> เมื่อเรามี interface NSCopying แล้วสิ่งที่ต้องทำต่อมาก็คือ implement ส่วนของ method ที่มีชื่อว่า -(id) copyWithZone: (NSZone*) zone แล้วภายใน method ก็ประกาศตัวแปร และก็ copy ค่าต่างๆมาให้ยัง object ใหม่ที่ทำการประกาศ เพียงเท่านี้ก็เสร็จ แล้วครับ

    ลอง compile และ run ดูจะเห็นว่า myclass_B นั้นมีค่าเหมือนกับ myclass_A ทุกอย่าง

    งงตรงไหน ติชม คอมเม้นกันมาได้นะครับ

Leave a Reply