NSObject类提供了两个用于拷贝的方法:- (id)copy 和- (id)mutableCopy,这两个方法都可以复制已有对象的副本。由于oc中几乎所有的类都继承自根类NSObject,所以类中都有copy和mutableCopy两个方法,那么是否就意味着拥有这两个方法的对象可以直接调用这两个方法进行拷贝了呢?
我们先定义一个AMPerson类继承自NSObject 进行测试,代码如下:
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [p1 copy];
运行程序,发生崩溃,并输出以下错误信息:
-[AMPerson copyWithZone:]: unrecognized selector sent to instance 0x7bf5e880 意思是:AMPerson类中找不到copyWithZone:方法。
把copy方法换成mutableCopy,AMPerson *p2 = [p1 mutableCopy],运行之后,依然发生崩溃,提示信息如下:
-[AMPerson mutableCopyWithZone:]: unrecognized selector sent to instance 0x7a2415f0 意思是:AMPerson类中找不到mutableCopyWithZone:方法。
由以上错误可知:拷贝操作表面是调用copy和mutableCopy方法,其实底层是调用对象自身的copyWithZone和mutableCopyWithZone方法来完成实际的复制工作。copy返回实际上就是copyWithZone:方法的返回值;mutableCopy与mutableCopyWithZone:方法也是同样的道理。
由该例就引出了下面的讨论内容了。就是对象具体要满足什么条件,才可以被复制。
分为两大类来讨论:首先,自定义对象的复制。
要想自定义对象可以复制,那么该类就必须
一,遵守NSCopying 或 NSMutableCopying协议。
二,实现协议中copyWithZone或者mutableCopyWithZone方法。
所以让我们的AMPerson类能够复制自身,我们需要让AMPerson实现NSCopying协议;然后实现copyWithZone:方法。
@interface AMPerson : NSObject <NSCopying>
@property (copy,nonatomic)NSString *name;
@end
@implementation AMPerson
- (id)copyWithZone:(NSZone *)zone {
AMPerson *p = [[[self class] allocWithZone:zone] init];
p.name = [self.name copy];
return p;
}
@end
然后再运行这两句代码
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [p1 copy];
NSLog(@"p1 = %p,p2 = %p",p1,p2);
打印结果为:p1 = 0x7969cc40,p2 = 0x7969c6e0
结果表明:p1和p2是两个地址不同的不同对象,复制操作成功。
其次,再来讨论系统的对象的复制
copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使对象本身是可修改的。例如,NSMutableString调用copy方法,将会返回不可修改的字符串对象。
mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的。例如,程序调用NSString的mutableCopy方法,将会返回一个NSMutableString对象
下图详细列出了NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等分别调用copy与mutableCopy方法后的结果。
深复制与浅复制
对象拷贝有两种方式:浅拷贝和深拷贝。浅拷贝,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深拷贝是直接拷贝整个对象内容到另一块内存中。再简单些说:浅拷贝就是指针拷贝,深拷贝就是内容拷贝。
接着,我们来讨论一下多层数组的复制。
如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深复制,还是浅复制?
如下所示
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [[AMPerson alloc] init];
AMPerson *p3 = [[AMPerson alloc] init];
NSArray *p = @[p1,p2,p3];//数组
NSArray *pCopy = [p mutableCopy];//复制
NSLog(@"p = %p,pCopy = %p",p,pCopy); //打印结果: p = 0x7c268090,pCopy = 0x7c26af20
NSLog(@"p = %p,pCopy = %p",p[0],pCopy[0]); //打印结果:p = 0x7c26a6e0,pCopy = 0x7c26a6e0
打印结果表明:数组复制只是单单对于数组对象本身而言是深复制,而数组的成员对象默认仍然是浅拷贝的。我们称之为单层深复制。
那么要想实现完全深复制该怎么办呢? 尤其是当该对象包含大量的指针类型的实例变量时,如果某些实例变量里再次包含指针类型的实例变量,那么实现完全深复制会更加复杂。上面的深复制就是因为集合对象中可能会包含指针类型的实例变量,从而导致深复制不完全。
把上面的复制代码NSArray *pCopy = [p mutableCopy]换成NSArray *pCopy = [[NSMutableArray alloc] initWithArray:p copyItems:YES]即可。
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [[AMPerson alloc] init];
AMPerson *p3 = [[AMPerson alloc] init];
NSArray *p = @[p1,p2,p3];
NSArray *pCopy = [[NSMutableArray alloc] initWithArray:p copyItems:YES];//复制
NSLog(@"p = %p,pCopy = %p",p,pCopy);//打印结果:p = 0x7a93af60,pCopy = 0x7a939680
NSLog(@"p = %p,pCopy = %p",p[0],pCopy[0]);//打印结果:p = 0x7a93c2e0,pCopy = 0x7a93c990
结果表明这次的复制是 完全深复制。不仅仅复制了第一层的数组对象,也复制了数组内部的指针类型的实例变量。当然内部的实例变量要遵守NSCoping协议。
总结
对于对象的深复制的概念没有必要那么纠结,只要我们理解了复制的本质,并且运用到我们的业务场景,选择我们想要的复制方式就可以。最主要的还是理解本质并且学会使用。