1.拷贝
The exact meaning of “copy” can vary from class to class, but a copy must be a functionally independent object with values identical to the original at the time the copy was made.
上面的摘要描述了拷贝
的三个基本特征:
拷贝可以作用于不同的对象上,比如值类型和指针类型、字符串和集合、可变对象和不可变对象等。对于不同的对象,拷贝有着不同的意义和效果,后面会继续介绍。
一个值类型对象被作为方法的参数传递或者作为返回值时,我们通常使用的是它的拷贝,而不是它本身。比如下面的方法中,我们将一个字符串赋值给对象的name
实例变量:
1 2 3 4 5 - (void )setName:(NSString *)aName { [name autorelease]; name = [aName copy ]; }
保存aName
的拷贝产生的效果就是:产生了一个独立的对象,对象的值与原对象一致。后续对原对象的操作不会影响到新对象,对新对象的操作也不会影响到原对象。再比如,我们通常会在一个方法中返回某个对象的拷贝,而不是对象本身:
1 2 3 4 - (NSString *)name { return [[name copy ] autorelease]; }
这里name
的 getter 返回的就是name
的一份拷贝~
上面说了,拷贝之后,原对象与新对象相互独立,互不影响。新对象的值与发生拷贝时原对象的值保持一致,后续原对象的值怎么变化,并不影响新对象的值。
2.协议 1.拷贝协议 NSObject.h
中定义了两个关于拷贝的实例方法:
The copy method is defined for all NSObjects and simply invokes copyWithZone: with the default zone.
copy
方法是一个便利方法,用来调用copyWithZone:
方法,返回一个不可变对象。后者是NSCopying
协议的协议方法。如果一个继承自 NSObject 的类的实例对象要调用copy
时,那么该类就必须实现此协议,否则会报异常。
Convenience method for classes that adopt the NSMutableCopying protocol. This method just calls the NSMutableCopying protocol method mutableCopyWithZone: with the zone as NULL. An exception is raised if there is no implementation for mutableCopyWithZone:.
与copy
一样,mutableCopy
也是一个便利方法,用来调用NSMutableCopying
协议的协议方法mutableCopyWithZone:
。此方法主要用来返回一个可变对象。同样的,对象需要调用mutableCopy
时,其所属类必须实现此协议,并在协议方法中返回一个可变对象。只有那些有可变和不可变之分的类才需要实现此协议,否则应该实现NSCopying
协议。如果某个类既有可变版本又有不可变版本,那就需要同时实现这两个协议。
2.拷贝与继承 A
、B
两个类,B继承自A,如果A中没有实现NSCopying
或NSMutableCopying
协议,而B实现了,则 B 类的协议方法中既需要拷贝自己声明的属性,又需要拷贝从父类继承而来的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @interface Model : NSObject @property (nonatomic , copy ) NSString *name;@end @implementation Model @end @interface SubModel : Model <NSCopying >@property (nonatomic , copy ) NSString *subName;@end @implementation SubModel -(id )copyWithZone:(NSZone *)zone { SubModel *newSubModel = [[[self class ] alloc] init]; newSubModel.name = [self .name copy ]; newSubModel.subName = [self .subName copy ]; return newSubModel; }@end
如果A类中实现了拷贝协议,则B会继承此协议,B只需在自己的类中重写协议方法,调用super
实现父类属性的拷贝,并拷贝自己类中声明的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @interface Model : NSObject <NSCopying >@property (nonatomic , copy ) NSString *name;@end @implementation Model -(id )copyWithZone:(NSZone *)zone { Model *newModel = [[[self class ] allocWithZone:zone] init]; newModel.name = [self .name copy ]; return newModel; }@interface SubModel : Model @property (nonatomic , copy ) NSString *subName;@end @implementation SubModel -(id )copyWithZone:(NSZone *)zone { SubModel *newSubModel = [super copyWithZone:zone]; newSubModel.subName = [self .subName copy ]; return newSubModel; }
3.可/不可变对象 copy
返回的是不可变对象,mutableCopy
返回的是可变对象。按照 Apple文档 的说法,对象默认是可变的。这很好理解,比如你自定义的客户
实体,当客户的信息发生变化时,对应的实体对象应该能更新相关字段。大多数的对象都允许你通过 setter 函数修改它内部封装的数据,Foundation 框架也为我们提供了一些可变类型,如:
NSMutableArray
NSMutableDictionary
NSMutableSet
NSMutableIndexSet
NSMutableCharacterSet
NSMutableData
NSMutableString
NSMutableAttributedString
NSMutableURLRequest
既然对象默认是可变的,为什么还要有不可变对象呢?这是因为,不可变对象可以防止被多处引用时因一方的修改导致其他所有引用的地方都意外地发生变化的情况,比如你持有的 tableview 数据源数组是可变的,如果在别的地方被引用且其中的数据被清空了,那么你的列表就会出问题。另外,不可变对象在性能上更有优势,因为它们不需要像可变对象那样始终维持一份可变存储。
我们通常会上面提到的集合类或者 NSString 和 block 等对象发送这两个消息,从而获得一个新的拷贝对象,这些操作的背后是框架帮我们处理了协议方法的实现及其返回值。
对于我们自定义的类的实例,也可以调用这俩方法,但是,需要我们自己实现对应的协议方法,自己定义返回值。协议方法内的返回值可以是 self,也可以是一个属性值与 self 属性值一致的新对象,根据你的需求而定。
小结: 结合以上分析可以看出,这两个方法只是一种便利方法
。当向对象发送这两个消息时,它们只是会在对象所属的类中查找并调用对应的协议方法,返回可变或不可变的新对象。这点类似于我们常用的便利初始化函数
,最终调用指定初始化函数。从另一个角度来说,copy
并不等价于浅拷贝,mutableCopy
也并等价于深拷贝,尤其是当你在自定义的类中实现拷贝的协议方法时,深拷贝还是浅拷贝可根据你的需求而定。具体什么是深拷贝和浅拷贝,接下来继续介绍。
3.深/浅拷贝 二者在处理简单的纯量属性(Int,float等)时,都是直接拷贝属性的值到新对象;不同点在于对指针类型的处理上:
浅拷贝 :指针拷贝,将原对象的指针复制到副本中,原对象和副本共享指针指向的数据。
深拷贝 :对象拷贝,复制原指针指向的数据给副本,副本与原对象的地址不同,互不影响。
4.字符串 1.不可变字符串 结论:对不可变字符串的copy是浅拷贝,mutableCopy是深拷贝。
1 2 3 4 5 6 7 NSString *str1 = @"hello" ;NSString *str2 = [str1 copy ]; NSString *str3 = [str1 mutableCopy]; NSLog (@"str1 = %p" ,str1);NSLog (@"str2 = %p" ,str2);NSLog (@"str3 = %p" ,str3);
日志:
1 2 3 str1 = 0 x10ae55810str2 = 0 x10ae55810str3 = 0 x600000258ea0
str1 与 str2 指向同一片内存区域,就是说对不可变字符串的 copy 只是指针拷贝;而 str3 与 str1 指向不同内存区域,说明对不可变字符串的 mutableCopy 产生了新的对象,此对象的内存地址与 str1 指向的地址完全不同。
2.可变字符串 结论:对可变字符串的copy与mutableCopy都是深拷贝。
1 2 3 4 5 6 7 NSMutableString *str1 = [NSMutableString stringWithString:@"hello" ];NSString *str2 = [str1 copy ]; NSString *str3 = [str1 mutableCopy]; NSLog (@"str1 = %p" ,str1);NSLog (@"str2 = %p" ,str2);NSLog (@"str3 = %p" ,str3);
日志:
1 2 3 str1 = 0 x604000451040str2 = 0 xa00006f6c6c65685str3 = 0 x604000450fe0
对可变字符串的两种复制都产生了新的对象,三个对象的地址完全不同。
3.可变性 结论:字符串,不论可不可变,copy 后返回不可变对象;mutableCopy 后返回可变对象。
1 2 3 4 5 6 7 8 9 10 NSString *str1 = @"hi" ;NSMutableString *str2 = [str1 mutableCopy]; [str2 appendString:@"xx" ];NSMutableString *str3 = [NSMutableString stringWithString:@"hello" ];NSMutableString *str4 = [str3 copy ]; NSMutableString *str5 = [str3 mutableCopy]; [str4 appendString:@"world" ]; [str5 appendString:@"Kitty" ];
示例在运行到 [str4 appendString:@”world”] 这一行时会报错“unrecognized selector sent to instance”,这就是说可变字符串在执行 copy 操作后返回的对象不再是可变字符串,因此对其执行 appendString 方法时出现了崩溃。注释掉这一行后,程序正常执行。
5.自定义类 结论:对于我们自定义的类,其 copy 与 mutableCopy 是深/浅拷贝因我们的实现而定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @interface Model : NSObject <NSCopying , NSMutableCopying >@property (nonatomic , copy ) NSString *name;@end #import "Model.h" @implementation Model - (id )copyWithZone:(NSZone *)zone { Model *newModel = [[[self class ] allocWithZone:zone] init]; newModel.name = [self .name copy ]; return newModel; } - (id )mutableCopyWithZone:(NSZone *)zone { Model *newModel = [[[self class ] allocWithZone:zone] init]; newModel.name = [self .name mutableCopy]; return newModel; }@end
调用:
1 2 3 4 5 6 7 8 9 Model *model = [Model new];model .name = @"sakura" ; NSLog(@"++++model = %p;" ,model );Model *copymodel = [model copy]; NSLog(@"++++copymodel = %p" ,copymodel);Model *mutableCopymodel = [model mutableCopy]; NSLog(@"++++mutableCopymodel = %p" ,mutableCopymodel);
日志:
6.集合类 1.浅拷贝 对OC中集合类对象的拷贝,默认是浅拷贝。集合的浅拷贝会产生新的集合对象,新旧集合内的元素指针相同。原集合中的每个元素会收到一条 retain 消息,引用计数+1,它们的指针会被拷贝到新建的集合中。系统为我们提供了一些方法,以实现集合对象的浅拷贝:
1 2 3 4 -copyWithZone:(如 [anArr copyWithZone:nil ];) -mutableCopyWithZone: -initWithArray:copyItems:NO (第二个参数为NO ) -initWithDictionary:copyItems:NO (第二个参数为NO )
2.深拷贝 集合的深拷贝会产生新的集合对象,新旧集合内的元素指针不相同,新集合中的元素是从原集合中的元素拷贝而来。
方案1:通过集合类对象的实例方法
1 2 - initWithArray:copyItems:YES (第二个参数为YES ) - initWithDictionary:copyItems:YES (第二个参数为YES )
copyItems 的参数 = YES,这种方式下集合中的每个对象都会调用一次 copyWithZone: 方法,如果这些对象实现了 NSCopying 协议,那么这些对象会被深拷贝到新集合中。如果没有实现 NSCopying 协议,则运行时会报错。
方案2:通过归档解档
If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol.
1 2 NSArray* trueDeepCopyArray = [NSKeyedUnarchive r unarchive ObjectWithData: [NSKeyedArchive r archive dDataWithRootObject:oldArray]]
这是集合真正意义上的深拷贝,这种方式下集合中的所有对象都要实现 NSCoding 协议,不然也会出现崩溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @interface Model : NSObject <NSCopying ,NSMutableCopying >@property (nonatomic , copy ) NSString *name;@end @implementation Model - (id )copyWithZone:(NSZone *)zone { NSLog (@"++++call Model copyWithZone~" ); Model *newModel = [[[self class ] allocWithZone:zone] init]; newModel.name = [self .name copy ]; return newModel; } - (id )mutableCopyWithZone:(NSZone *)zone { NSLog (@"++++call Model mutableCopyWithZone~" ); Model *newModel = [[[self class ] allocWithZone:zone] init]; newModel.name = [self .name mutableCopy]; return newModel; }@end
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 Model *model = [[Model alloc ] init]; NSArray *array = [NSArray arrayWithObject :model ] ;NSLog(@"++++model:%p" ,model ) ;NSLog(@"+++array:%p,+++array[0]:%p" ,array ,array [0]) ; NSArray *zoneArray = [array copyWithZone :nil ] ;NSLog(@"+++zoneArray:%p,+++zoneArray[0]:%p" ,zoneArray ,zoneArray [0]) ; NSArray *noCopyArr = [[NSArray alloc ] initWithArray:array copyItems:NO];NSLog(@"+++noCopyArr:%p,+++noCopyArr[0]:%p" ,noCopyArr ,noCopyArr [0]) ; NSArray *copyArray = [[NSArray alloc ] initWithArray:array copyItems:YES];NSLog(@"+++copyArray:%p,+++copyArray[0]:%p" ,copyArray ,copyArray [0]) ;
日志:
1 2 3 4 5 6 ++++ +++ , +++ [ ] +++ , +++ [ ] +++ , +++ [ ] ++++ +++ , +++ [ ]
日志信息显示:
copyWithZone:返回的的数组对象与原数组对象的指针相同,内部元素的指针也相同;
copyItems:NO返回的数组对象与原数组对象的指针不同,内部元素的指针相同;
copyItems:YES返回的数组对象与原数组对象的指针不同,内部元素的指针也不相同,且自动调用了元素的拷贝协议方法;
这些都印证了上面关于集合对象深浅拷贝时内部元素的不同。
3.可变性
上层不可变,其他更深层对象的可变性与原对象相同。
1 @property (nonatomic , copy ) NSMutableArray * array;
对array
属性执行add
操作会发生什么?
copy
方法返回的集合是不可变集合,故而这里array
属性实际上是不可变的,对其执行增删对象操作时会闪退。
1 - initWithArray:copyItems:NO:
上层可变性与其初始化时的类型保持一致,其他更深层的可变性与原对象相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - (void )testWithNoCopy { NSMutableArray *element = [[NSMutableArray alloc] initWithObjects:@(1 ), nil ]; NSLog (@"++++element:%p,elementClass:%@" ,element,[element class ]); NSMutableArray *mutArr1 = [[NSMutableArray alloc] initWithObjects:element, nil ]; NSLog (@"++++mutArr1:%p" ,mutArr1); NSMutableArray *mutArr2 = [[NSMutableArray alloc] initWithArray:mutArr1 copyItems:NO ]; [mutArr2 addObject:@(2 )]; NSLog (@"++++mutArr2:%p, mutArr2Class:%@, mutArr2.count:%lu" ,mutArr2,[mutArr2 class ],mutArr2.count); NSMutableArray *arrAt0 = mutArr2[0 ]; NSLog (@"++++arrAt0:%p, arrAt0Class:%@" ,arrAt0,[arrAt0 class ]); [arrAt0 addObject:@(3 )]; NSLog (@"++++arrAt0.count:%lu" ,(unsigned long )arrAt0.count); NSLog (@"++++element.count:%lu" ,(unsigned long )element.count); }
日志:
1 2 3 4 5 6 ++++element:0x60000229de00 ,elementClass:__NSArrayM ++++mutArr1:0 x600002283810 ++++mutArr2:0 x6000022838d0, mutArr2Class:__NSArrayM , mutArr2.count:2 ++++arrAt0:0 x60000229de00, arrAt0Class:__NSArrayM ++++arrAt0.count:2 ++++element.count:2
mutArr2 是通过copyItems:NO产生的新数组,内部元素从可变数组 mutArr1 中拷贝而来。示例中 [mutArr2 addObject:@(2)] 可以正常执行,说明copyItems:NO产生的 mutArr2 正如其创建时声明的那样,是一个可变数组,即:上层可变性与其初始化时的类型保持一致~
arrAt0 作为 mutArr2 的子元素,指针与 element 相同,也就是说在创建 mutArr2 时只是拷贝了 mutArr1 中元素 element 的指针。因此,arrAt0 也是一个可变数组,能正常执行 [arrAt0 addObject:@(3)],即:其他更深层的可变性与原对象相同。
1 - initWithArray:copyItems:YES:
上层可变性与初始化时的类型一致,接下来的一层不可变,其他更深层的可变性与原对象相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (void )testWithCopy { NSMutableArray *inArr = [[NSMutableArray alloc] initWithObjects:@(1 ), nil ]; NSLog (@"++++inArr:%p, inArrClass:%@" ,inArr,[inArr class ]); NSMutableArray *element = [[NSMutableArray alloc] initWithObjects:inArr, nil ]; NSLog (@"++++element:%p,elementClass:%@" ,element,[element class ]); NSMutableArray *mutArr1 = [[NSMutableArray alloc] initWithObjects:element, nil ]; NSLog (@"++++mutArr1:%p" ,mutArr1); NSMutableArray *mutArr2 = [[NSMutableArray alloc] initWithArray:mutArr1 copyItems:YES ]; [mutArr2 addObject:@(2 )]; NSLog (@"++++mutArr2:%p, mutArr2Class:%@, mutArr2.count:%lu" ,mutArr2,[mutArr2 class ],mutArr2.count); NSMutableArray *arrAt0 = mutArr2[0 ]; NSLog (@"++++arrAt0:%p, arrAt0Class:%@" ,arrAt0,[arrAt0 class ]); NSMutableArray *inArrAt0 = arrAt0[0 ]; NSLog (@"++++inArrAt0:%p, inArrAt0Class:%@" ,inArrAt0,[inArrAt0 class ]); [inArrAt0 addObject:@(3 )]; NSLog (@"++++inArrAt0.count:%lu" ,(unsigned long )inArrAt0.count); NSLog (@"++++inArr.count:%lu" ,(unsigned long )inArr.count); }
日志:
1 2 3 4 5 6 7 8 ++++inArr:0 x6000028a4150, inArrClass:__NSArrayM ++++element:0x6000028bd0b0 ,elementClass:__NSArrayM ++++mutArr1:0 x6000028bd3e0 ++++mutArr2:0 x6000028bd470, mutArr2Class:__NSArrayM , mutArr2.count:2 ++++arrAt0:0 x6000024e19d0, arrAt0Class:__NSSingleObjectArrayI ++++inArrAt0:0 x6000028a4150, inArrAt0Class:__NSArrayM ++++inArrAt0.count:2 ++++inArr.count:2
可以看到,mutArr2Class:__NSArrayM,说明 mutArr2 仍然是一个可变数组,即:上层可变性与其初始化时的类型保持一致~
arrAt0Class:__NSSingleObjectArrayI,且 arrAt0 与 element 的指针不再相同,说明 arrAt0 是内容拷贝而非指针拷贝,且不可变,即:接下来的一层不可变~
inArrAt0Class:__NSArrayM,inArrAt0 为 arrAt0 中的第一个元素,对应着 inArr,且二者的指针相同,即:其他更深层的可变性与原对象相同。
另外,mutArr2 是一个集合,算是最上层。arrAt0 作为 mutArr2 集合的第一层,对应着 mutArr1 中的 element,但 arrAt0 与 element 的指针不同,也就是说 initWithArray:copyItems:YES 产生的集合中,最上层是深拷贝;inArrAt0 作为 arrAt0 的元素,是 mutArr2 的第二层,它的指针与 inArr 的指针相同,且 [inArrAt0 addObject:@(3)] 之后 inArr.count 也随着变化,这说明从第二层开始就已经是指针拷贝,即浅拷贝了~
结论:集合的深拷贝,只是单层深拷贝,更深层开始元素就只是指针拷贝了~
所有层的可变性与原对象完全相同。
4.copy与mutableCopy 1.不可变集合 结论:对于不可变集合对象的 copy 是指针拷贝,不产生新对象;mutableCopy 是内容拷贝,会产生新对象;两种情况下集合内元素都是指针拷贝。
1 2 3 4 5 6 7 8 9 Model *m = [[Model alloc ] init]; NSArray *element = @[m ] ; NSArray *oriArray = @[element ] ; NSMutableArray *copyArray = [oriArray copy ] ; NSMutableArray *mutableCopyArray = [oriArray mutableCopy ] ; NSLog(@"+++oriArr:%p,element:%p" ,oriArray ,element ) ;NSLog(@"++++copyArr:%p,indexAt0:%p" ,copyArray ,copyArray [0]) ;NSLog(@"++++mutableCopyArr:%p,indexAt0:%p" ,mutableCopyArray ,mutableCopyArray [0]) ;
日志:
1 2 3 +++oriArr:0 x6000039f0760 ,element:0 x6000039f06b0 ++++copyArr:0 x6000039f0760 ,indexAt0:0 x6000039f06b0 ++++mutableCopyArr:0 x6000035a59b0 ,indexAt0:0 x6000039f06b0
不可变数组 oriArray 中包含了一个数组对象 element 。从日志可以看出,oriArray 与从其 copy 出来的数组 copyArray 指向同一片内存,而与从其 mutableCopy 出来的数组 mutableCopyArray 的内存不同。这与不可变非集合类对象类似,对不可变数组的 copy 只是指针拷贝,不会产生新的集合对象;mutableCopy 是内容拷贝,会产生新的集合对象。
2.可变集合 结论:对于可变集合对象的 copy 和 mutableCopy 都是内容拷贝,会产生新的集合对象;两种情况下集合内元素都是指针拷贝。
1 2 3 4 5 6 7 8 9 Model *m = [[Model alloc] init];NSArray *element = @[m];NSMutableArray *oriArray = [NSMutableArray arrayWithObject:element];NSMutableArray *copyArray = [oriArray copy ];NSMutableArray *mutableCopyArray = [oriArray mutableCopy]; NSLog (@"+++oriArr:%p,element:%p" ,oriArray,element);NSLog (@"++++copyArr:%p,indexAt0:%p" ,copyArray,copyArray[0 ]);NSLog (@"++++mutableCopyArr:%p,indexAt0:%p" ,mutableCopyArray,mutableCopyArray[0 ]);
日志:
1 2 3 +++oriArr:0 x60000102dd10,element:0 x600001c64c40 ++++copyArr:0 x600001c73360 ,indexAt0:0 x600001c64c40 ++++mutableCopyArr:0 x600001033900 ,indexAt0:0 x600001c64c40
对可变数组 oriArray 的两种拷贝都是内容拷贝,都产生了新的集合对象。
上面两个示例中,元素 element 与 copyArray[0] 和 mutableCopyArray[0] 都是指向同一片内存。也就是说两种拷贝下,新旧集合内容部的元素都只是指针拷贝。
结论:对于可变和不可变集合对象的copy 和 mutableCopy,其内部元素始终都是指针拷贝。
3.集合元素的指针拷贝 1 2 3 4 5 6 7 8 9 10 11 12 NSArray *elementArr = [NSMutableArray arrayWithObject:@1 ];NSMutableArray *oriArray = [NSMutableArray arrayWithObject:elementArr];NSMutableArray *mutableCopyArray = [oriArray mutableCopy]; [mutableCopyArray[0 ] addObject:@2 ];NSLog (@"++++objects in element:%p++++" ,elementArr);for (NSNumber *num in elementArr) { NSLog (@"++++%d" ,[num intValue]); }NSLog (@"++++objects in mutableCopyArray[0]:%p++++" ,mutableCopyArray[0 ]);for (NSNumber *num in mutableCopyArray[0 ]) { NSLog (@"++++%d" ,[num intValue]); }
日志:
1 2 3 4 5 6 ++++ ++++ ++++ ++++ ++++ [ ] ++++ ++++ ++++
示例显示,mutableCopyArray 内的元素与 oriArray 内的元素,都是指向同一片地址,也就是 element 的地址。后续对 mutableCopyArray[0] 执行添加数据的操作时,element 数组也受到影响,跟着增加了相同的对象。因此实际开发过程中,需要留意这一点!
7.小结
可变对象的copy
和mutableCopy
都是深拷贝;
不可变对象的copy
是浅拷贝,mutableCopy
是深拷贝;
copy
方法返回的都是不可变对象;
相关参考:
#©Apple-Documentation-nscopying
#©Apple-Documentation-nscopying新
#©Apple-Documentation-nsmutablecopying
#©Apple-Documentation-Object Mutability