นี่ก็น่าจะเป็นเรื่อง objective-c แบบเพียวๆเกือบๆจะสุดท้ายละ ก็เหลืออีกไม่กี่เรื่องที่ผมคิดว่าน่าจะเพียงพอสำหรับการเริ่มต้นกับ cocoa เอาละเข้าเรื่องเลยดีกว่า
โดยปกติเราเวลาที่เราประกาศตัวแปรขึ้นมาสักตัว ถ้าไม่ใช่ class ก็จะเป็น primitive type เป็นต้นว่า int , char , double การที่เราจะ copy ข้อมูลจาก ตัวแปรหนึ่งไปยังอีกตัวแปร หนึ่งนั้นทำได้ง่ายมาก เพียงแค่ใช้เครื่องหมาย = แค่นี้เองเช่นเป็นต้นว่า
int a = 10;
int b;
b = a;
b = b+10;
a = a + 15;
NSLog(@”%d %d”,a,b);
จาก code เบื้องต้นจะเห็นว่า เราได้ให้ b มีค่าเท่ากับ a และเพิ่มค่า b ด้วย 10 แ่น่นอนว่า จาก code ข้างบนนี้ ค่า a กับ b มีค่าต่างกันคือ 20 กับ 25 สรุปง่ายๆว่า เราเปลี่ยนค่า a ก็ไม่ได้มีผลกระทบอะไรกับค่า b และในทำนองเดียวกัน ถ้าเราทำการแก้ไขค่าของ b ก็ไม่ได้มีผลอะไรกับ a
แต่การประกาศ class instance นั้นถ้าเราเขียนโปรแกรมในลักษณะเหมือนๆกับ ข้างบน ดังเช่นตัวอย่างข้างล่าง
#import <Foundation/Foundation.h>
int main()
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableString *helloString = [[NSMutableString alloc] initWithCString:“Hello”];
NSMutableString *temp;
temp = helloString ;
[temp appendString:@" World"];
NSLog(@”%s”, [helloString UTF8String] ) ;
NSLog(@”%s”, [temp UTF8String] ) ;
[pool drain];
return 0;
}
เราได้ประกาศ instance ของ NSMutableString มา 2 ตัวคือ helloString กับ temp และเราก็ได้ให้ ค่า temp มีค่าเท่ากับ helloString นั่นก็แปลว่า temp ควรจะมีค่า เป็น Hello จากนั้น เราก็ได้เปลี่ยนแปลงค่า ของ temp โดยการต่อท้ายด้วย World และเราก็ให้พิมพ์ค่าตัวแปร helloString ออกมา แต่ผลปรากฎว่า มันกลับพิมพ์คำว่า Hello World ออกมา ทั้งๆที่เราก็ไม่ได้ไปทำการแก้ไขค่าใน ตัวแปร helloString เลยสักนิด
เพราะอะไรละ ทำไมถึงเป็นแบบนี้ ?
เพราะว่า การประกาศ class instance นั้นเป็นการประกาศ pointer ของ class นั้นๆ ฉนั้นการที่เขียน code ว่า
temp = helloString
ก็เป็นการบอกว่า ให้ temp นั้นชี้ไปยังตำแหน่ง memory เดียวกันกับ helloString ฉนั้นแล้วเมื่อมันเป็น ตำแหน่ง memory เดียวกัน ถ้าหากเราทำการแก้ไข temp ก็แปลได้ว่า helloString ก็ต้องเปลี่ยนไปด้วย
เหมือนในภาษา c/c++ถ้าดู code ต่อไปนี้
char helloString[64] = “Hello World”;
char temp[64];
temp = helloString;
มันก็แปลได้ว่า เราให้ temp ชี้ไปที่ helloString ไม่ได้เป็นการ copy ค่า ถ้าหากเราจะ ให้ค่า temp เป็น Hello World เหมือนกับ helloString เราต้องใช้ ฟังชั่นที่ชื่อว่า strcpy ทำการ copy string ถ้าเป็นภาษา c/c++ อาจจะเขียนได้แบบนี้
strcpy(temp, helloString);
ปล. ใครที่ยังไม่เข้าใจเรื่อง 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 ได้ยังไง
#import <Foundation/Foundation.h>
int main()
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableString *helloString = [[NSMutableString alloc] initWithCString:“Hello”];
NSMutableString *temp;
[temp mutableCopy:helloString] ;
[temp appendString:@" World"];
NSLog(@”%s”, [helloString UTF8String] ) ;
NSLog(@”%s”, [temp UTF8String] ) ;
[pool drain];
return 0;
}
ถ้าเรา compile และ run ดูจะเห็นว่าผลลัพธ์ที่ได้จะออกมาเป็น
Hello
HelloWorld
ซึ่งจะต่างจากโปรแกรมแรกที่เราเขียน เพราะว่า เราทำการ copy ค่าจาก helloSting มาให้ยัง temp ( การ copy นี้หมายถึงการคัดลอก memory ) นั่นก็แปลว่าค่าของตัวแปรทั้งสองอันนี้แยกจากกัน ฉนั้นถ้าเราแก้ไขตัวแปร temp ก็ไม่ได้เกี่ยวอะไรกับ helloString
Shallow - Deep
อย่างที่บอกไปแล้วว่า instance นั้นคือ pointer ดีๆนี่เอง ลองพิจารณา code ข้างล่างนี้
#import <Foundation/Foundation.h>
int main()
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableString *helloString = [[NSMutableString alloc] initWithCString:“Hello”];
NSMutableArray *arrayOne = [[NSMutableArray alloc] init];
NSMutableArray *arrayTwo;
[arrayOne addObject:helloString];
arrayTwo = [arrayOne mutableCopy] ;
[[arrayTwo objectAtIndex:0] appendString:@” World”];
NSLog(@”%s”, [[arrayOne objectAtIndex:0] UTF8String] ) ;
[pool drain];
return 0;
}
จากโปรแกรมข้างบน เราเริ่มด้วยการประกาศตัวแปรมาทั้ง 3 ตัวคือ arrayOne arrayTwo และ helloString หลังจากนั้น เราก็ได้เอาตัวแปร helloString ไปไว้ที่ arrayOne แล้วหลังจากนั้นก็ arrayTwo ก็ได้ copy 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> เท่านั้นเอง มาดูกันเลยดีกว่า ว่ามันหน้าตาเป็นยังไง
#import <Foundation/Foundation.h>
@interface MyClass : NSObject <NSCopying>
{
int m_number;
}
-(void) SetValue: (int) num;
-(int) GetValue;
@end
@implementation MyClass
-(void) SetValue: (int) num
{
m_number = num;
}
-(int) GetValue
{
return m_number;
}
-(id) copyWithZone: (NSZone*) zone
{
MyClass *temp = [[MyClass allocWithZone:zone] init];
[temp SetValue: m_number];
return temp;
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyClass *myclass_A = [[MyClass alloc] init];
MyClass *myclass_B;
[myclass_A SetValue:9];
myclass_B = [myclass_A copy];
NSLog(@”%d”,[myclass_B GetValue] );
[pool drain];
return 0;
}
ก็ดูจาก code ข้างบนจะเห็นว่า ตอนที่ประกาศ class จะมีส่วนเพิ่มเติมเข้ามาคือ <NSCopying> เมื่อเรามี interface NSCopy แล้วสิ่งที่ต้องทำต่อมาก็คือ implement ส่วนของ method ที่มีชื่อว่า -(id) copyWithZone: (NSZone*) zone แล้วภายใน method ก็ประกาศตัวแปร และก็ copy ค่าต่างๆมาให้ยัง object ใหม่ที่ทำการประกาศ เพียงเท่านี้ก็เสร็จ แล้วครับ
ลอง compile และ run ดูจะเห็นว่า myclass_B นั้นมีค่าเหมือนกับ myclass_A ทุกอย่าง
งงตรงไหน ติชม คอมเม้นกันมาได้นะครับ