NSObject類提供了兩個用于拷貝的方法:- (id)copy 和- (id)mutableCopy,這兩個方法都可以復(fù)制已有對象的副本。由于oc中幾乎所有的類都繼承自根類NSObject,所以類中都有copy和mutableCopy兩個方法,那么是否就意味著擁有這兩個方法的對象可以直接調(diào)用這兩個方法進行拷貝了呢?
我們先定義一個AMPerson類繼承自NSObject 進行測試,代碼如下:
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [p1 copy];
運行程序,發(fā)生崩潰,并輸出以下錯誤信息:
-[AMPerson copyWithZone:]: unrecognized selector sent to instance 0x7bf5e880? 意思是:AMPerson類中找不到copyWithZone:方法。
把copy方法換成mutableCopy,AMPerson *p2 = [p1 mutableCopy],運行之后,依然發(fā)生崩潰,提示信息如下:
-[AMPerson mutableCopyWithZone:]: unrecognized selector sent to instance 0x7a2415f0 意思是:AMPerson類中找不到mutableCopyWithZone:方法。
由以上錯誤可知:拷貝操作表面是調(diào)用copy和mutableCopy方法,其實底層是調(diào)用對象自身的copyWithZone和mutableCopyWithZone方法來完成實際的復(fù)制工作。copy返回實際上就是copyWithZone:方法的返回值;mutableCopy與mutableCopyWithZone:方法也是同樣的道理。
由該例就引出了下面的討論內(nèi)容了。就是對象具體要滿足什么條件,才可以被復(fù)制。
分為兩大類來討論:首先,自定義對象的復(fù)制。
要想自定義對象可以復(fù)制,那么該類就必須
一,遵守NSCopying 或 NSMutableCopying協(xié)議。
二,實現(xiàn)協(xié)議中copyWithZone或者mutableCopyWithZone方法。
所以讓我們的AMPerson類能夠復(fù)制自身,我們需要讓AMPerson實現(xiàn)NSCopying協(xié)議;然后實現(xiàn)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);
打印結(jié)果為:p1 = 0x7969cc40,p2 = 0x7969c6e0
結(jié)果表明:p1和p2是兩個地址不同的不同對象,復(fù)制操作成功。
其次,再來討論系統(tǒng)的對象的復(fù)制
copy方法用于復(fù)制對象的副本。通常來說,copy方法總是返回對象的不可修改的副本,即使對象本身是可修改的。例如,NSMutableString調(diào)用copy方法,將會返回不可修改的字符串對象。
mutableCopy方法用于復(fù)制對象的可變副本。通常來說,mutableCopy方法總是返回對象可修改的副本,即使被復(fù)制的對象本身是不可修改的。例如,程序調(diào)用NSString的mutableCopy方法,將會返回一個NSMutableString對象
下圖詳細列出了NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等分別調(diào)用copy與mutableCopy方法后的結(jié)果。

深復(fù)制與淺復(fù)制
對象拷貝有兩種方式:淺拷貝和深拷貝。淺拷貝,并不拷貝對象本身,僅僅是拷貝指向?qū)ο蟮闹羔?;深拷貝是直接拷貝整個對象內(nèi)容到另一塊內(nèi)存中。再簡單些說:淺拷貝就是指針拷貝,深拷貝就是內(nèi)容拷貝。
接著,我們來討論一下多層數(shù)組的復(fù)制。
如果在多層數(shù)組中,對第一層進行內(nèi)容拷貝,其它層進行指針拷貝,這種情況是屬于深復(fù)制,還是淺復(fù)制?
如下所示
AMPerson *p1 = [[AMPerson alloc] init];
AMPerson *p2 = [[AMPerson alloc] init];
AMPerson *p3 = [[AMPerson alloc] init];
NSArray *p = @[p1,p2,p3];//數(shù)組
NSArray *pCopy = [p mutableCopy];//復(fù)制
NSLog(@"p = %p,pCopy = %p",p,pCopy); //打印結(jié)果: p = 0x7c268090,pCopy = 0x7c26af20
NSLog(@"p = %p,pCopy = %p",p[0],pCopy[0]); //打印結(jié)果:p = 0x7c26a6e0,pCopy = 0x7c26a6e0
打印結(jié)果表明:數(shù)組復(fù)制只是單單對于數(shù)組對象本身而言是深復(fù)制,而數(shù)組的成員對象默認仍然是淺拷貝的。我們稱之為單層深復(fù)制。
那么要想實現(xiàn)完全深復(fù)制該怎么辦呢? 尤其是當(dāng)該對象包含大量的指針類型的實例變量時,如果某些實例變量里再次包含指針類型的實例變量,那么實現(xiàn)完全深復(fù)制會更加復(fù)雜。上面的深復(fù)制就是因為集合對象中可能會包含指針類型的實例變量,從而導(dǎo)致深復(fù)制不完全。
把上面的復(fù)制代碼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];//復(fù)制
NSLog(@"p = %p,pCopy = %p",p,pCopy);//打印結(jié)果:p = 0x7a93af60,pCopy = 0x7a939680
NSLog(@"p = %p,pCopy = %p",p[0],pCopy[0]);//打印結(jié)果:p = 0x7a93c2e0,pCopy = 0x7a93c990
結(jié)果表明這次的復(fù)制是 完全深復(fù)制。不僅僅復(fù)制了第一層的數(shù)組對象,也復(fù)制了數(shù)組內(nèi)部的指針類型的實例變量。當(dāng)然內(nèi)部的實例變量要遵守NSCoping協(xié)議。
總結(jié)
對于對象的深復(fù)制的概念沒有必要那么糾結(jié),只要我們理解了復(fù)制的本質(zhì),并且運用到我們的業(yè)務(wù)場景,選擇我們想要的復(fù)制方式就可以。最主要的還是理解本質(zhì)并且學(xué)會使用。