集合類型的拷貝
深拷貝是深度拷貝,是拷貝一個(gè)實(shí)例對象到一個(gè)新的內(nèi)存地址攘残,而淺拷貝只是簡單拷貝一個(gè)實(shí)例對象的指針插佛。蘋果官方文檔提供了如下圖由于理解深拷貝和淺拷貝
由上圖可知产弹,集合的淺拷貝(shallow copy)后的數(shù)組Array2與之前的數(shù)組Array1指向同一段內(nèi)存區(qū)域两疚,而深拷貝(deep copy)下Array2和Array1指向不同的內(nèi)存區(qū)域床估。
淺拷貝
集合淺拷貝的方式有很多膀跌。當(dāng)創(chuàng)建一個(gè)淺拷貝時(shí)嘲驾,之前集合里面的每個(gè)對象都會(huì)收到retain消息样漆,使其引用計(jì)數(shù)加1将宪,并且將其指針拷貝到新集合中。
NSArray *array = @[@"1111"];
NSArray *shallowCopyArray = [array copyWithZone:nil];
NSLog(@"array address: %p", array);
NSLog(@"shallowCopyArray address: %p", shallowCopyArray);
NSDictionary *dic = @{@"key": @"value"};
NSDictionary *shallowCopyDic = [[NSDictionary alloc]initWithDictionary:dic copyItems:NO];
NSLog(@"dic address: %p", dic);
NSLog(@"shallowCopyDic address: %p", shallowCopyDic);
運(yùn)行輸出
array address: 0x60800001f700
shallowCopyArray address: 0x60800001f700
dic address: 0x6080002212c0
shallowCopyDic address: 0x6080002215c0
可以發(fā)現(xiàn)淺拷貝前后的數(shù)組所指向的內(nèi)存地址一樣知态,而字典所指向的內(nèi)存地址發(fā)生了變化识腿,為何同樣是淺拷貝,拷貝前后地址卻發(fā)生了改變呢励烦?
這是因?yàn)閷τ跀?shù)組,我們只是調(diào)用它的copyWithZone
方法泼诱,由于是不可變數(shù)組坛掠,返回自身,所以淺拷貝前后數(shù)組的內(nèi)存地址不變治筒。
而對于字典來說屉栓,shallowCopyDic
是通過alloc
、init
創(chuàng)建的耸袜,因此在內(nèi)存中開辟了一段新的內(nèi)存友多,但對于之前字典中的對象,只是拷貝其內(nèi)存地址堤框,所以淺拷貝前后字典的內(nèi)存地址發(fā)生了變化域滥,其實(shí)內(nèi)部元素的地址是不變的纵柿。
因此,在集合對象的淺拷貝中启绰,并非是對于自身的淺拷貝昂儒,而是對內(nèi)部元素的拷貝
深拷貝
在深拷貝中,系統(tǒng)會(huì)向集合中的每一個(gè)元素發(fā)生一個(gè)copyWithZone
消息委可,該消息是來自NSCoping
協(xié)議渊跋,如果有對象沒有實(shí)現(xiàn)該協(xié)議方法,那么就會(huì)奔潰着倾,如果實(shí)現(xiàn)了該方法拾酝,那么會(huì)根據(jù)該方法的具體實(shí)現(xiàn),實(shí)現(xiàn)具體的深拷貝卡者。
NSString *str = @"222";
NSArray * array = @[str];
NSArray *shalldowCopyArray = [array copyWithZone:nil];
NSArray *deepCopyArray = [[NSArray alloc]initWithArray:array copyItems:YES];
NSLog(@"array address: %p", array);
NSLog(@"shalldowCopyArray address: %p" , shalldowCopyArray);
NSLog(@"deepCopyArray address: %p", deepCopyArray);
NSLog(@"array[0] address: %p", array[0]);
NSLog(@"shalldowCopyArray[0] address: %p", shalldowCopyArray[0]);
NSLog(@"deepCopyArray[0] address: %p", deepCopyArray[0]);
運(yùn)行輸出
array address: 0x6000000158a0
shalldowCopyArray address: 0x6000000158a0
deepCopyArray address: 0x6000000158b0
array[0] address: 0x10ece8158
shalldowCopyArray[0] address: 0x10ece8158
deepCopyArray[0] address: 0x10ece8158
打印前3行與我們的猜測一致微宝,但是后面3行卻打印這相同的地址。這有些意外虎眨,明明采用了深拷貝和淺拷貝蟋软,結(jié)果卻是相同的內(nèi)存地址,為什么會(huì)這樣呢嗽桩?
是因?yàn)榧项愋蜕羁截悤?huì)對每一個(gè)元素調(diào)用copyWithZone
方法岳守,這意味著后面3行最終打印輸出什么取決于該方法。在深拷貝時(shí)碌冶,對于第一個(gè)元素調(diào)用了copyWithZone
方法湿痢,但是由于NSString是不可變的,對于其深拷貝創(chuàng)建一個(gè)新內(nèi)存是無意義的扑庞,所以我們可以猜測NSString的copyWithZone
方法也是直接返回self
譬重,所以淺拷貝時(shí)是直接拷貝元素地址,而深拷貝是通過copyWithZone
方法來獲取元素地址罐氨,兩個(gè)結(jié)果是一樣的臀规。
如果將str的類型改動(dòng)一下,將其改為NSMutableString類型:
NSMutableString *str = [[NSMutableString alloc]initWithString:@"222"];
就可以看到打印的元素地址發(fā)生了改變
array[0] address: 0x6080002641c0
shalldowCopyArray[0] address: 0x6080002641c0
deepCopyArray[0] address: 0xa000000003232323
對于深拷貝來說栅隐,自身如何拷貝取決于實(shí)例方法中copyWithZone
如何實(shí)現(xiàn)塔嬉,對于下一級還是采用淺拷貝方式,這稱為集合類型的單層深拷貝
完全拷貝
除了淺拷貝和深拷貝租悄,還有一個(gè)完全拷貝的概念谨究。那么什么是完全拷貝呢?就是對對象的每一層都是重新創(chuàng)建實(shí)例變量泣棋,不存在指針拷貝胶哲。舉個(gè)例子,在對數(shù)組進(jìn)行歸檔和解檔時(shí)潭辈,其實(shí)就是完全拷貝鸯屿。
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
完全拷貝不僅是在歸檔俩檬、解檔中存在,在其他場景也存在碾盟。
對于以上三種拷貝類型總結(jié):
淺拷貝:在淺拷貝操作時(shí)棚辽,對于被拷貝對象的每一層進(jìn)行指針拷貝
深拷貝:在深拷貝操作時(shí),對于被拷貝對象冰肴,至少有一層是深拷貝
完全拷貝:在完成拷貝操作時(shí)屈藐,對于被拷貝對象的每一層都是深拷貝
非集合類型拷貝
不可變對象調(diào)用copy
和mutableCopy
NSString *string = @"1234";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
NSLog(@"string address: %p", string);
NSLog(@"stringCopy: %p", stringCopy);
NSLog(@"stringMCopy: %p", stringMCopy);
運(yùn)行輸出
string address: 0x10f83a218
stringCopy: 0x10f83a218
stringMCopy: 0x600000267540
可以看到,對NSString
進(jìn)行copy
只是對其指針進(jìn)行的拷貝熙尉,而進(jìn)行mutableCopy
是真正重新創(chuàng)建一個(gè)新的NSString對象联逻。注意:寫定的字符串是存放在內(nèi)存的常量區(qū),因此可以看到兩處的地址位置相差甚遠(yuǎn)检痰。
另外copy
方法是與NSCoping
協(xié)議相關(guān)的包归,mutableCopy
是與NSMutableCoping
協(xié)議相關(guān)的。對于NSString
這樣的不可變系統(tǒng)類來說铅歼,copy
后返回自身是比較好理解的公壤,所以copy
后仍然是相同的內(nèi)存地址。而對NSString
調(diào)用mutableCopy
表明你需要一個(gè)新的可變對象椎椰,所以會(huì)返回一個(gè)NSMutableString
對象厦幅。
可變對象的copy
和mutableCopy
NSMutableString *mString = [[NSMutableString alloc]initWithString:@"123"];
NSString *copyString = [mString copy];
NSString * mCopyString = [mString mutableCopy];
NSLog(@"mString address: %p", mString);
NSLog(@"copyString address: %p", copyString);
NSLog(@"mCopyString address: %p", mCopyString);
NSLog(@"copyString is mutable: %@", [copyString isKindOfClass:[NSMutableString class]] ? @"YES": @"No");
NSLog(@"mCopyString is mutable: %@", [mCopyString isKindOfClass:[NSMutableString class]] ? @"YES": @"No");
運(yùn)行輸出
mString address: 0x600000073e00
copyString address: 0xa000000003332313
mCopyString address: 0x600000074100
copyString is mutable: No
mCopyString is mutable: YES
從打印結(jié)果來看,對于NSMutableString
來說慨飘,其copy
的返回值是一個(gè)不可變的字符串确憨,而mutableCopy
返回的是可變字符串,即使三者是不一樣的地址瓤的,即三個(gè)對象休弃。
在Foundation
和UIkit
框架中,類似于NSString
和NSMutableString
這樣的非集合對象分為可變和不可變對象并不多圈膏,但是對于copy
和mutableCopy
的實(shí)現(xiàn)原理都是一樣的塔猾,即:對可變對象的copy
是不可變對象,使用mutableCopy
都為可變
[immutableObject copy]; //淺復(fù)制
[immutableObject mutableCopy]; //深復(fù)制
[mutableObject copy]; //深復(fù)制
[mutableObject mutableCopy]; //深復(fù)制
自定義拷貝
如果自定義的類需要實(shí)現(xiàn)淺拷貝本辐,則在實(shí)現(xiàn)copyWithZone
方法是返回自身即可,而如果需要實(shí)現(xiàn)深拷貝慎皱,則在copyWithZone
方法中創(chuàng)建一個(gè)新實(shí)例對象返回即可
對于所謂的深拷貝叶骨,其實(shí)應(yīng)當(dāng)取決于每一層對象本身,如果需要達(dá)到完全深拷貝忽刽,則每一層對象都應(yīng)當(dāng)在copyWithZone
方法中創(chuàng)建好新的實(shí)例對象天揖,如果每一層都為深拷貝夺欲,那么最外層拷貝就是完全拷貝了今膊。
copy屬性
在類中聲明屬性時(shí)有個(gè)copy
修飾符,一般用于修飾字符串和block
以及一些數(shù)組和字典斑唬。那么為什么要聲明為copy
市埋,而不是聲明為strong
呢?
@property(nonatomic, copy) NSMutableString *string;
self.string = [[NSMutableString alloc]initWithString:@"1234"];
NSString *copyString = self.string;
NSLog(@"copyString is mutable: %@", [copyString isKindOfClass:[NSMutableString class]] ? @"YES": @"NO");
NSLog(@"string is mutable: %@", [self.string isKindOfClass:[NSMutableString class]] ? @"YES": @"NO");
運(yùn)行輸出
copyString is mutable: NO
string is mutable: NO
但是當(dāng)我們把copy
改為strong
恕刘,則會(huì)打印YES
.
這其實(shí)并不復(fù)雜,在使用copy
的時(shí)候褐着,會(huì)對屬性的setter
方法進(jìn)行判斷,對屬性進(jìn)行copy
频敛,根據(jù)屬性是否可變馅扣,則與前面說到的邏輯相同,如果屬性可變岂嗓,則返回一個(gè)新的不可變對象,即為不可變字符串厌殉,而對于不可變則直接返回self,即為不可變字符串
如果是strong
器紧,則是直接對其引用楼眷,并沒有執(zhí)行copy
方法,這就是區(qū)別罐柳。如果屬性緩存數(shù)組或者字典张吉,原理是一樣的。
對于block
稍微有些不同,因?yàn)?code>MRC中创南,block
需要顯示地copy
到堆上省核,而ARC
中如果引用外部變量賦值則會(huì)自動(dòng)拷貝到內(nèi)存中,所以block
在ARC
下使用copy
和strong
無異气忠。
對于NSString
來說,作為不可變對象笔刹,修飾符為copy
時(shí),執(zhí)行copy
方法返回自身舌菜,strong
修飾也是返回自身,所以對于NSString
這樣的不可變對象來說袱瓮,使用strong
和copy
也是一樣的爱咬。
參考