前言##
NSObject類提供了copy和mutableCopy方法,通過這兩個方法即可復制已有對象的副本,本文將會詳細介紹關(guān)于對象復制的內(nèi)容。
系統(tǒng)對象的copy與mutableCopy##
copy方法用于復制對象的副本。通常來說,copy方法總是返回對象的不可修改的副本,即使對象本身是可修改的。例如,程序調(diào)用NSMutableString的copy方法,將會返回不可修改的字符串對象,
mutableCopy方法用于復制對象的可變副本。通常來說,mutableCopy方法總是返回對象可修改的副本,即使被復制的對象本身是不可修改的,調(diào)用mutableCopy方法復制出來的副本也是可修改的。例如,程序調(diào)用NSString的mutableCopy方法,將會返回一個NSMutableString對象。
下圖詳細闡述了NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary分別調(diào)用copy與mutableCopy方法后的結(jié)果:

系統(tǒng)對象復制
深復制與淺復制##
對象拷貝有兩種方式:淺復制和深復制。顧名思義,淺復制,并不拷貝對象本身,僅僅是拷貝指向?qū)ο蟮闹羔?;深復制是直接拷貝整個對象內(nèi)容到另一塊內(nèi)存中。再簡單些說:淺復制就是指針拷貝;深復制就是內(nèi)容拷貝。
如果在多層數(shù)組中,對第一層進行內(nèi)容拷貝,其它層進行指針拷貝,這種情況是屬于深復制,還是淺復制?對此,蘋果官網(wǎng)文檔有這樣一句話描述:
This kind ofcopyis only capable of producing a one-level-deepcopy.If you only need a one-level-deepcopy...If you need atruedeepcopy,such as when you have an array of arrays...
從文中可以看出,蘋果認為這種復制不是真正的深復制,而是將其稱為單層深復制(one-level-deep copy)。因此,有人對淺復制、完全深復制、單層深復制做了概念區(qū)分。當然,這些都是概念性的東西,沒有必要糾結(jié)于此。只要知道進行拷貝操作時,被拷貝的是指針還是內(nèi)容即可。
一般來說,完全深復制的實現(xiàn)難度大很多,尤其是當該對象包含大量的指針類型的實例變量時,如果某些實例變量里再次包含指針類型的實例變量,那么實現(xiàn)完全深復制會更加復雜。
上面圖中的深復制(單層或者完全)就是因為集合對象中可能會包含指針類型的實例變量,從而導致深復制不完全。
自定義對象的復制##
使用copy和mutableCopy復制對象的副本使用起來確實方便,那么我們自定義的類是否可調(diào)用copy與mutableCopy方法來復制副本呢?
我們先定義一個SXYPerson類,代碼如下:
@interfaceSXYPerson:NSObject@property(nonatomic,assign)NSInteger age;@property(nonatomic,copy)NSString*name;@end
然后嘗試調(diào)用SXYPerson的copy方法來復制一個副本:
SXYPerson*person1=[[SXYPerson alloc]init];//創(chuàng)建一個SXYPerson對象person1.age=20;person1.name=@"蘇小妖";SXYPerson*person2=[person1copy];//復制副本
運行程序,將會發(fā)生崩潰,并輸出以下錯誤信息:
[SXYPersoncopyWithZone:]:unrecognized selector sent to instance0x608000030920
上面的提示:SXYPerson找不到copyWithZone:方法。
我們將復制副本的代碼換成如下:
SXYPerson*person2=[person1 mutableCopy];//復制副本
再次運行程序,程序同樣崩潰了,并輸出去以下錯誤信息:
[SXYPersonmutableCopyWithZone:]:unrecognized selector sent to instance0x600000221120
上面的提示:SXYPerson找不到mutableCopyWithZone:方法。
大家可能會覺得疑惑,程序只是調(diào)用了copy和mutableCopy方法,為什么會提示找不到copyWithZone:與mutableCopyWithZone:方法呢?其實當程序調(diào)用對象的copy方法來復制自身時,底層需要調(diào)用copyWithZone:方法來完成實際的復制工作,copy返回實際上就是copyWithZone:方法的返回值;mutableCopy與mutableCopyWithZone:方法也是同樣的道理。
那么怎么做才能讓自定義的對象進行copy與mutableCopy呢?需要做以下事情:
1.讓類實現(xiàn)NSCopying/NSMutableCopying協(xié)議。2.讓類實現(xiàn)copyWithZone:/mutableCopyWithZone:方法
所以讓我們的SXYPerson類能夠復制自身,我們需要讓SXYPerson實現(xiàn)NSCopying協(xié)議;然后實現(xiàn)copyWithZone:方法:
@interfaceSXYPerson:NSObject<NSCopying>@property(nonatomic,assign)NSInteger age;@property(nonatomic,copy)NSString*name;@end
#import"SXYPerson.h"@implementationSXYPerson-(id)copyWithZone:(NSZone*)zone{SXYPerson*person=[[[selfclass]allocWithZone:zone]init];person.age=self.age;person.name=self.name;returnperson;}@end
運行之后發(fā)現(xiàn)我們實現(xiàn)了對象的復制:

自定義對象復制
同時需要注意的是如果對象中有其他指針類型的實例變量,且只是簡單的賦值操作:person.obj2 = self.obj2,其中obj2是另一個自定義類,如果我們修改obj2中的屬性,我們會發(fā)現(xiàn)復制后的person對象中obj2對象中的屬性值也變了,因為對于這個對象并沒有進行copy操作,這樣的復制操作不是完全的復制,如果要實現(xiàn)完全的復制,需要將obj2對應的類也要實現(xiàn)copy,然后這樣賦值:person.obj2 = [self.obj2 copy]。如果對象很多或者層級很多,實現(xiàn)起來還是很麻煩的。如果需要實現(xiàn)完全復制同樣還有另有一種方法,那就是歸檔:
SXYPerson*person2=[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:person1]];
這樣我們就實現(xiàn)了自定義對象的復制,需要指出的是如果重寫copyWithZone:方法時,其父類已經(jīng)實現(xiàn)NSCopying協(xié)議,并重寫過了copyWithZone:方法,那么子類重寫copyWithZone:方法應先調(diào)用父類的copy方法復制從父類繼承得到的成員變量,然后對子類中定義的成員變量進行賦值:
-(id)copyWithZone:(NSZone*)zone{id obj=[supercopyWithZone:zone];//對子類定義的成員變量賦值...returnobj;}
關(guān)于mutableCopy的實現(xiàn)與copy的實現(xiàn)類似,只是實現(xiàn)的是NSMutableCopying協(xié)議與mutableCopyWithZone:方法。對于自定義的對象,在我看來并沒有什么可變不可變的概念,因此實現(xiàn)mutableCopy其實是沒有什么意義的,在此就不詳細介紹了。
定義屬性的copy指示符##
如下段代碼,我們在定義屬性的時候使用了copy指示符:
#import@interfaceSXYPerson:NSObject<NSCopying>@property(nonatomic,copy)NSMutableString*name;@end
使用如下代碼來進行測試:
SXYPerson*person1=[[SXYPersonalloc]init];//創(chuàng)建一個SXYPerson對象person1.name=[NSMutableStringstringWithString:@"蘇小妖"];[person1.name appendString:@"123"];
運行程序會崩潰,并且提示以下信息:
***Terminating app due to uncaught exception'NSInvalidArgumentException',reason:'Attempt to mutate immutable object with appendString:'
這段錯誤提示不允許修改person的name屬性,這是因為程序定義name屬性時使用了copy指示符,該指示符置頂調(diào)用setName:方法時(通過點語法賦值時,實際上是調(diào)用對應的setter方法),程序?qū)嶋H上會使用參數(shù)的副本對name實際變量復制。也就是說,setName:方法的代碼如下:
-(void)setName:(NSMutableString*)name{_name=[name copy];}
copy方法默認是復制該對象的不可變副本,雖然程序傳入的NSMutableString,但程序調(diào)用該參數(shù)的copy方法得到的是不可變副本。因此,程序賦給SXYPerson對象的name實例變量的值依然是不可變字符串。
注意:定義合成getter、setter方法時并沒有提供mutableCopy指示符。因此即使定義實例變量時使用了可變類型,但只要使用copy指示符,實例變量實際得到的值總是不可變對象。
總結(jié)##
對于對象的深復制的概念沒有必要那么糾結(jié),只要我們理解了復制的本質(zhì),并且運用到我們的業(yè)務場景,選擇我們想要的復制方式就可以。最主要的還是理解本質(zhì)并且學會使用。
參考鏈接:http://www.itdecent.cn/p/ac07c26f467d