來源: https://www.zybuluo.com/MicroCai/note/50592
概念
對象拷貝有兩種方式:淺復制和深復制兄渺。顧名思義择浊,淺復制,并不拷貝對象本身刹碾,僅僅是拷貝指向對象的指針;深復制是直接拷貝整個對象內(nèi)存到另一塊內(nèi)存中座柱。
一圖以蔽之
再簡單些說:淺復制就是指針拷貝教硫;深復制就是內(nèi)容拷貝。
集合的淺復制 (shallow copy)
集合的淺復制有非常多種方法辆布。當你進行淺復制時瞬矩,會向原始的集合發(fā)送retain消息,引用計數(shù)加1锋玲,同時指針被拷貝到新的集合景用。
現(xiàn)在讓我們看一些淺復制的例子:
NSArray*shallowCopyArray=[someArray copyWithZone:nil];
NSSet*shallowCopySet=[NSSetmutableCopyWithZone:nil];
NSDictionary*shallowCopyDict=[[NSDictionaryalloc]initWithDictionary:someDictionary copyItems:NO];
集合的深復制 (deep copy)
集合的深復制有兩種方法〔氧澹可以用 initWithArray:copyItems: 將第二個參數(shù)設置為YES即可深復制伞插,如
NSDictionaryshallowCopyDict=[[NSDictionaryalloc]initWithDictionary:someDictionary copyItems:YES];
如果你用這種方法深復制,集合里的每個對象都會收到 copyWithZone: 消息盾碗。如果集合里的對象遵循 NSCopying 協(xié)議媚污,那么對象就會被深復制到新的集合。如果對象沒有遵循 NSCopying 協(xié)議廷雅,而嘗試用這種方法進行深復制耗美,會在運行時出錯。copyWithZone: 這種拷貝方式只能夠提供一層內(nèi)存拷貝(one-level-deep copy)航缀,而非真正的深復制商架。
第二個方法是將集合進行歸檔(archive),然后解檔(unarchive)芥玉,如:
NSArray*trueDeepCopyArray=[NSKeyedUnarchiverunarchiveObjectWithData:[NSKeyedArchiverarchivedDataWithRootObject:oldArray]];
集合的單層深復制 (one-level-deep copy)
看到這里蛇摸,有同學會問:如果在多層數(shù)組中,對第一層進行內(nèi)容拷貝灿巧,其它層進行指針拷貝赶袄,這種情況是屬于深復制,還是淺復制抠藕?對此饿肺,蘋果官網(wǎng)文檔有這樣一句話描述
This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy...
If you need a true deep copy, such as when you have an array of arrays...
從文中可以看出,蘋果認為這種復制不是真正的深復制幢痘,而是將其稱為單層深復制(one-level-deep copy)唬格。因此,網(wǎng)上有人對淺復制、深復制购岗、單層深復制做了概念區(qū)分汰聋。
淺復制(shallow copy):在淺復制操作時,對于被復制對象的每一層都是指針復制喊积。
深復制(one-level-deep copy):在深復制操作時烹困,對于被復制對象,至少有一層是深復制乾吻。
完全復制(real-deep copy):在完全復制操作時髓梅,對于被復制對象的每一層都是對象復制。
當然绎签,這些都是概念性的東西枯饿,沒有必要糾結于此。只要知道進行拷貝操作時诡必,被拷貝的是指針還是內(nèi)容即可奢方。
系統(tǒng)對象的copy與mutableCopy方法
不管是集合類對象,還是非集合類對象爸舒,接收到copy和mutableCopy消息時蟋字,都遵循以下準則:
copy返回imutable對象;所以扭勉,如果對copy返回值使用mutable對象接口就會crash鹊奖;
mutableCopy返回mutable對象;
下面將針對非集合類對象和集合類對象的copy和mutableCopy方法進行具體的闡述
1涂炎、非集合類對象的copy與mutableCopy
系統(tǒng)非集合類對象指的是 NSString, NSNumber ... 之類的對象忠聚。下面先看個非集合類immutable對象拷貝的例子
NSString*string=@"origin";
NSString*stringCopy=[stringcopy];
NSMutableString*stringMCopy=[stringmutableCopy];
通過查看內(nèi)存,可以看到 stringCopy 和 string 的地址是一樣璧尸,進行了指針拷貝咒林;而 stringMCopy 的地址和 string 不一樣,進行了內(nèi)容拷貝爷光;
再看mutable對象拷貝例子
NSMutableString*string=[NSMutableStringstringWithString:@"origin"];
//copy
NSString*stringCopy=[stringcopy];
NSMutableString*mStringCopy=[stringcopy];
NSMutableString*stringMCopy=[stringmutableCopy];
//change value
[mStringCopy appendString:@"mm"];//crash
[stringappendString:@" origion!"];
[stringMCopy appendString:@"!!"];
運行以上代碼,會在第7行crash澎粟,原因就是 copy 返回的對象是 immutable 對象蛀序。注釋第7行后再運行,查看內(nèi)存活烙,發(fā)現(xiàn) string徐裸、stringCopy、mStringCopy啸盏、stringMCopy 四個對象的內(nèi)存地址都不一樣重贺,說明此時都是做內(nèi)容拷貝。
綜上兩個例子,我們可以得出結論:
在非集合類對象中:對immutable對象進行copy操作气笙,是指針復制次企,mutableCopy操作時內(nèi)容復制;對mutable對象進行copy和mutableCopy都是內(nèi)容復制潜圃。用代碼簡單表示如下:
[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //深復制
[mutableObject copy] //深復制
[mutableObject mutableCopy] //深復制
2缸棵、集合類對象的copy與mutableCopy
集合類對象是指NSArray、NSDictionary谭期、NSSet ... 之類的對象堵第。下面先看集合類immutable對象使用copy和mutableCopy的一個例子:
NSArray*array=@[@[@"a",@"b"],@[@"c",@"d"];
NSArray*copyArray=[array copy];
NSMutableArray*mCopyArray=[array mutableCopy];
查看內(nèi)容,可以看到copyArray和array的地址是一樣的隧出,而mCopyArray和array的地址是不同的踏志。說明copy操作進行了指針拷貝,mutableCopy進行了內(nèi)容拷貝胀瞪。但需要強調(diào)的是:此處的內(nèi)容拷貝针余,僅僅是拷貝array這個對象,array集合內(nèi)部的元素仍然是指針拷貝赏廓。這和上面的非集合immutable對象的拷貝還是挺相似的涵紊,那么mutable對象的拷貝會不會類似呢?我們繼續(xù)往下幔摸,看mutable對象拷貝的例子:
NSMutableArray*array=[NSMutableArrayarrayWithObjects:[NSMutableStringstringWithString:@"a"],@"b",@"c",nil];
NSArray*copyArray=[array copy];
NSMutableArray*mCopyArray=[array mutableCopy];
查看內(nèi)存摸柄,如我們所料,copyArray既忆、mCopyArray和array的內(nèi)存地址都不一樣驱负,說明copyArray、mCopyArray都對array進行了內(nèi)容拷貝患雇。同樣跃脊,我們可以得出結論:
在集合類對象中,對immutable對象進行copy苛吱,是指針復制酪术,mutableCopy是內(nèi)容復制;對mutable對象進行copy和mutableCopy都是內(nèi)容復制翠储。但是:集合對象的內(nèi)容復制僅限于對象本身绘雁,對象元素仍然是指針復制。用代碼簡單表示如下:
[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //單層深復制
[mutableObject copy] //單層深復制
[mutableObject mutableCopy] //單層深復制
這個代碼結論和非集合類的非常相似援所。
這時候庐舟,是不是有人要問了,如果要對集合對象復制元素怎么辦住拭?有這疑問的同學不妨回頭看看集合的深復制挪略。
好了历帚,深復制與淺復制就講到這里。
最后說個題外的東西杠娱,在搜集資料的過程中挽牢,發(fā)現(xiàn)一個有可能犯錯的點
NSString*str=@"string";
str=@"newString";
上面這段代碼,在執(zhí)行第二行代碼后墨辛,內(nèi)存地址發(fā)生了變化卓研。乍一看,有點意外睹簇。按照 C 語言的經(jīng)驗奏赘,初始化一個字符串之后,字符串的首地址就被確定下來太惠,不管之后如何修改字符串內(nèi)容磨淌,這個地址都不會改變。但此處第二行并不是對 str 指向的內(nèi)存地址重新賦值凿渊,因為賦值操作符左邊的 str 是一個指針梁只,也就是說此處修改的是內(nèi)存地址。
所以第二行應該這樣理解:將@"newStirng"當做一個新的對象埃脏,將這段對象的內(nèi)存地址賦值給str搪锣。
我有如下的兩個方法查看內(nèi)存地址
p str會打印對象本身的內(nèi)存地址和對象內(nèi)容
(lldb)p str
(NSString*)$0=0x000000010c913680@"a"
po &str則打印的是引用對象的指針所在的地址
(lldb)po&str
0x00007fff532fb6c0
參考文檔
[1]Apple Collections Programming Topics: Copying Collections
[2]iPhone Dev:iOS開發(fā)之深拷貝與淺拷貝(mutableCopy與Copy)詳解(博文可能有紕漏,童鞋仔細閱讀親自驗證)