ObjectiveC中的赋值,对象拷贝,浅拷贝与深拷贝

在开发过程中我们经常会遇到对象拷贝的问题,下面我们分别讨论赋值操作、对象拷贝、以及浅拷贝(Shallow copy)与深拷贝(Deep copy)的区别与各自的实现方式。


一、不同对象的赋值操作

Objective-C中有两类对象,一类是结构体(或者基本数据类型也算),另一类是NSObject对象。

对于结构体,代码直接会操作其实体,因此赋值操作会创建一个源对象的副本(一个新的对象);而对于NSObject对象,必须使用指针来操作对象,所以其赋值操作相当于复制了指针,而非对象,也就是说赋值操作使得源指针和新指针都指向同一个NSObject对象。这样讲有些难以理解,请看下面的代码:

[plain]  view plain copy
  1. // main.m  
  2.   
  3. #import <Foundation/Foundation.h>  
  4.   
  5. @interface TestObject : NSObject  
  6. {  
  7.     @public  
  8.     int x;  
  9.     int y;  
  10. }  
  11. @end  
  12.   
  13. @implementation TestObject  
  14. @end  
  15.   
  16. typedef struct TestStruct  
  17. {  
  18.     int x;  
  19.     int y;  
  20. }  
  21. TestStruct;  
  22.   
  23. int main(int argc, const char * argv[])  
  24. {  
  25.   
  26.     @autoreleasepool {  
  27.           
  28.         TestStruct ts1 = {100, 50};  
  29.         NSLog(@"ts1: %p, %d, %d", &ts1, ts1.x, ts1.y);  
  30.           
  31.         TestStruct ts2 = ts1;  
  32.         NSLog(@"ts2: %p, %d, %d", &ts2, ts2.x, ts2.y);  
  33.   
  34.         TestObject* to1 = [[[TestObject alloc] init] autorelease];  
  35.         NSLog(@"to1: %p, %d, %d", to1, to1->x, to1->y);  
  36.           
  37.         TestObject* to2 = to1;  
  38.         NSLog(@"to2: %p, %d, %d", to2, to2->x, to2->y);  
  39.           
  40.     }  
  41.     return 0;  
  42. }  

程序的运行结果如下:

[plain]  view plain copy
  1. ts1: 0x7fff63463898, 100, 50  
  2. ts2: 0x7fff63463890, 100, 50  
  3. to1: 0x7fc342d00370, 0, 0  
  4. to2: 0x7fc342d00370, 0, 0  
程序代码首先定义了一个类TestObject(继承自NSObject),然后又定义了一个结构体TestStruct。这两者都包含两个整型的成员变量x和y。然后在main函数中,程序首先为TestStruct结构体ts1分配内存空间,并为其成员变量赋初值,x为100,y为50。然后通过NSLog函数打印出该结构体的地址和成员变量的值,即输出的第一行内容。接着,程序执行了赋值语句,将ts1赋值给另一个TestStruct结构体对象ts2,这条语句会为ts2分配另一块内存,然后把ts1的每个成员变量的值复制过来。第二行输出也可以看出来,地址不一样了,所以如果修改ts1的成员变量的值,是不会影响ts2的。

接着再来看TestObject。程序接着使用alloc静态方法分配了一块新的内存空间,然后通过init实例方法进行初始化(所有成员变量的值为0),最后将该内存空间的首地址返回。to1的实质就是一个指针,指向创建的TestObject对象。接着,程序将to1赋值给to2。to2也是一个指向TestObject对象的指针,其值与to1一样,即两者都指向同一个对象。所以在这种情况下,对to1的修改会同时影响to2。

二、对象拷贝

Foundation框架的NSObject类提供了两个方法,分别是copy和mutableCopy方法,用于对NSObject对象进行拷贝操作。copy方法会调用NSCopying协议的copyWithZone:方法,而mutableCopy会调用 NSMutableCopying协议的mutableCopyWithZone:方法。将上面的代码修改如下:

[plain]  view plain copy
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface TestObject : NSObject  
  4. {  
  5.     @public  
  6.     int x;  
  7.     int y;  
  8. }  
  9. @end  
  10.   
  11. @implementation TestObject  
  12. - (NSString*)description  
  13. {  
  14.     return [NSString stringWithFormat:@"%@: %p, x: %d, y: %d", [self class], self, x, y];  
  15. }  
  16. @end  
  17.   
  18. typedef struct TestStruct  
  19. {  
  20.     int x;  
  21.     int y;  
  22. }  
  23. TestStruct;  
  24.   
  25. int main(int argc, const char * argv[])  
  26. {  
  27.     @autoreleasepool  
  28.     {          
  29.         TestObject* to1 = [[[TestObject alloc] init] autorelease];  
  30.         to1->x = 100; to1->y = 50;  
  31.         TestObject* to2 = [[[TestObject alloc] init] autorelease];  
  32.         to2->x = 200; to2->y = 400;  
  33.         TestObject* to3 = [[[TestObject alloc] init] autorelease];  
  34.         to3->x = 300; to3->y = 500;  
  35.           
  36.         //创建包含to1, to2, to3的数组array1  
  37.         NSArray* array1 = [NSArray arrayWithObjects:to1, to2, to3, nil];  
  38.         NSLog(@"array1: %p, \n%@", array1, array1);  
  39.           
  40.         //array2是array1调用copy的结果  
  41.         NSArray* array2 = [array1 copy];  
  42.         NSLog(@"array2: %p, \n%@", array2, array2);  
  43.         [array2 release];  
  44.           
  45.         //mutableArray2是array1调用mutableCopy的结果  
  46.         NSMutableArray* mutableArray2 = [array1 mutableCopy];  
  47.         NSLog(@"mutableArray2: %@, %p, \n%@", [mutableArray2 class], mutableArray2, mutableArray2);  
  48.         [mutableArray2 removeLastObject];  
  49.           
  50.         NSLog(@"After remove last object of mutableArray2");  
  51.           
  52.         NSLog(@"array1: %p, \n%@", array1, array1);  
  53.         NSLog(@"array2: %p, \n%@", array2, array2);  
  54.         NSLog(@"mutableArray2: %p, \n%@", mutableArray2, mutableArray2);  
  55.           
  56.         //mutableArray3是mutableArray2调用mutableCopy的结果  
  57.         NSMutableArray* mutableArray3 = [mutableArray2 mutableCopy];  
  58.         NSLog(@"mutableArray3: %p, \n%@", mutableArray3, mutableArray3);  
  59.         [mutableArray2 release];  
  60.           
  61.         //array4是mutableArray3调用copy的结果  
  62.         NSArray* array4 = [mutableArray3 copy];  
  63.         NSLog(@"array4: %@, %p, \n%@", [array4 class], array4, array4);  
  64.         [mutableArray3 release];  
  65.         [array4 release];  
  66.     }  
  67.     return 0;  
  68. }  


程序的运行结果如下:

[plain]  view plain copy
  1. 2012-03-22 19:20:49.548 ObjectCopy[18042:403] array1: 0x7f9071414820,   
  2. (  
  3.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  4.     "TestObject: 0x7f90714141c0, x: 200, y: 400",  
  5.     "TestObject: 0x7f9071414230, x: 300, y: 500"  
  6. )  
  7. 2012-03-22 19:20:49.550 ObjectCopy[18042:403] array2: 0x7f9071414820,   
  8. (  
  9.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  10.     "TestObject: 0x7f90714141c0, x: 200, y: 400",  
  11.     "TestObject: 0x7f9071414230, x: 300, y: 500"  
  12. )  
  13. 2012-03-22 19:20:49.551 ObjectCopy[18042:403] mutableArray2: __NSArrayM, 0x7f9072800000,   
  14. (  
  15.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  16.     "TestObject: 0x7f90714141c0, x: 200, y: 400",  
  17.     "TestObject: 0x7f9071414230, x: 300, y: 500"  
  18. )  
  19. 2012-03-22 19:20:49.552 ObjectCopy[18042:403] After remove last object of mutableArray2  
  20. 2012-03-22 19:20:49.552 ObjectCopy[18042:403] array1: 0x7f9071414820,   
  21. (  
  22.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  23.     "TestObject: 0x7f90714141c0, x: 200, y: 400",  
  24.     "TestObject: 0x7f9071414230, x: 300, y: 500"  
  25. )  
  26. 2012-03-22 19:20:49.553 ObjectCopy[18042:403] array2: 0x7f9071414820,   
  27. (  
  28.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  29.     "TestObject: 0x7f90714141c0, x: 200, y: 400",  
  30.     "TestObject: 0x7f9071414230, x: 300, y: 500"  
  31. )  
  32. 2012-03-22 19:20:49.553 ObjectCopy[18042:403] mutableArray2: 0x7f9072800000,   
  33. (  
  34.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  35.     "TestObject: 0x7f90714141c0, x: 200, y: 400"  
  36. )  
  37. 2012-03-22 19:20:49.557 ObjectCopy[18042:403] mutableArray3: 0x7f90729000d0,   
  38. (  
  39.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  40.     "TestObject: 0x7f90714141c0, x: 200, y: 400"  
  41. )  
  42. 2012-03-22 19:20:49.558 ObjectCopy[18042:403] array4: __NSArrayI, 0x7f9071416e70,   
  43. (  
  44.     "TestObject: 0x7f90714141b0, x: 100, y: 50",  
  45.     "TestObject: 0x7f90714141c0, x: 200, y: 400"  
  46. )  

程序的运行结果有几点值得注意,首先是array1与array2的地址相同,因为NSArray对象在创建之后是不可以修改的。其次,NSArray的mutableCopy方法会返回一个NSMutableArray对象。第三,对于NSArray或者NSMutableArray来说,mutableCopy方法会创建新的可变数组对象,但其每个数组成员的值仅仅是原数组的一个指针赋值,这就是浅拷贝。而与之相对的则是深拷贝,即复制数组时不是复制数组每个元素的引用,而是创建一个与之相同的新对象。第四,在NSArray对象上调用mutableCopy方法返回一个NSMutableArray对象,而在NSMutableArray对象上调用copy方法则返回一个NSArray对象,而不是NSMutableArray对象。

当然,以上讨论的是Foundation框架中的NSArray与NSMutableArray类,如果想要实现对自己创建的类的对象进行拷贝,则需要让类实现NSCopying协议。


三、实现对象的拷贝

对于我们自己创建的类来说,如果需要实现对象的拷贝,则需要实现NSCopying协议或者NSMutableCopying协议。前者用于实现对象拷贝,而后者则通常会返回一个可以进行修改的对象副本,例如Foundation框架中的常用容器类NSArray、NSSet等。这两个协议定义如下:

[plain]  view plain copy
  1. @protocol NSCopying  
  2. - (id)copyWithZone:(NSZone *)zone;  
  3. @end  
  4.   
  5. @protocol NSMutableCopying  
  6. - (id)mutableCopyWithZone:(NSZone *)zone;  
  7. @end  
copy方法与mutableCopy方法分别会调用copyWithZone:方法与mutableCopyWithZone:方法,并传入nil。将代码修改如下:

[plain]  view plain copy
  1. int main(int argc, const char * argv[])  
  2. {  
  3.     @autoreleasepool  
  4.     {          
  5.         TestObject* to1 = [[[TestObject alloc] init] autorelease];  
  6.         to1->x = 100; to1->y = 50;  
  7.   
  8.         TestObject* to2 = [to1 copy];  
  9.         NSLog(@"to2: %@", to2);  
  10.     }  
  11.     return 0;  
  12. }  
程序编译通过,但运行时抛出异常:

[plain]  view plain copy
  1. -[TestObject copyWithZone:]: unrecognized selector sent to instance 0x10e2141b0  
  2. *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject copyWithZone:]: unrecognized selector sent to instance 0x10e2141b0'  
原因是TestObject类尚未遵从NSCopying,因此无法进行拷贝。将TestObject类修改如下:

[plain]  view plain copy
  1. @interface TestObject : NSObject <NSCopying>  
  2. {  
  3.     @public  
  4.     int x;  
  5.     int y;  
  6. }  
  7. @end  
  8.   
  9. @implementation TestObject  
  10. - (NSString*)description  
  11. {  
  12.     return [NSString stringWithFormat:@"%@: %p, x: %d, y: %d", [self class], self, x, y];  
  13. }  
  14.   
  15. - (id)copyWithZone:(NSZone *)zone  
  16. {  
  17.     TestObject* newTestObject = [[TestObject allocWithZone:zone] init];  
  18.     newTestObject->x = self->x;  
  19.     newTestObject->y = self->y;  
  20.       
  21.     return newTestObject;  
  22. }  
  23. @end  
再次编译运行,这一次运行正常,输出如下结果:

[plain]  view plain copy
  1. to2: TestObject: 0x7ffaa84141c0, x: 100, y: 50  
注意,copyWithZone:方法调用allocWithZone:方法申请了内存空间并创建了一个新的对象,因此调用copy或者mutableCopy的代码需要对拷贝的对象进行内存的释放操作,忘记释放会导致内存泄漏。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值