一、從面向?qū)ο蟮絆bjective-C概覽copy
- 面向?qū)ο螅?/li>
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中质蕉,對(duì)象的copy就是創(chuàng)建一個(gè)已經(jīng)存在的對(duì)象的copy势篡。這種對(duì)象的創(chuàng)建的結(jié)果被稱(chēng)為原始對(duì)象的copy。copy是很基礎(chǔ)的模暗,但是也有其精巧的地方禁悠,并且可能造成巨大的消耗。有很多種方式可以copy對(duì)象兑宇,最常用的就是copy構(gòu)造器和克隆碍侦。copy經(jīng)常用于對(duì)象的修改、移動(dòng)和保護(hù)隶糕。如果上述的幾種應(yīng)用都不需要官硝,持有原始對(duì)象的引用就足夠了僵驰,并不需要copy。
- OC:
在OC中者春,copy和mutableCopy兩個(gè)方法是被所有對(duì)象繼承的(有點(diǎn)小毛病害淤,應(yīng)該指所有繼承自NSObject的類(lèi))唁影,這兩個(gè)方法就是為copy準(zhǔn)備的方篮。其中付秕,mutableCopy是為了創(chuàng)建原始對(duì)象的可變類(lèi)型的copy。這兩個(gè)方法分別調(diào)用copyWithZone和mutableCopyWithZone兩個(gè)方法來(lái)進(jìn)行copy霎冯。一個(gè)類(lèi)必須實(shí)現(xiàn)copyWithZone或者mutableCopyWithZone铃拇,才能進(jìn)行copy或者mutableCopy钞瀑。
那么沈撞,我們可以從以上獲取到什么信息?
copy經(jīng)常用于對(duì)象的修改、移動(dòng)和保護(hù)雕什。如果上述的幾種應(yīng)用都不需要缠俺,持有原始對(duì)象的引用就足夠了显晶,并不需要copy。
一個(gè)類(lèi)必須實(shí)現(xiàn)copyWithZone或者mutableCopyWithZone壹士,才能進(jìn)行copy或者mutableCopy磷雇。
下一階段,本文將展開(kāi)講述OC中的copy相關(guān)信息以及如何使用copy方法躏救。
二唯笙、Objective-C中copy相關(guān)
1、OC中的copy相關(guān)內(nèi)容
- 在XCode 里Foundation.framework下的Headers里盒使,也在系統(tǒng)里找到原文件:/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSObject.h
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
- 在/usr/include/objc 下面找到 runtime 的 NSObject.h
- (id)copy;
- (id)mutableCopy;
- 修飾屬性的關(guān)鍵字copy
2崩掘、這里需要注意的有以下幾點(diǎn)
若想使用copy和mutableCopy,需要分別實(shí)現(xiàn)NSCopying協(xié)議和NSMutableCopying協(xié)議少办,即實(shí)現(xiàn)copyWithZone:和mutableCopyWithZone:方法苞慢。
繼承自NSObject的大部分框架類(lèi)均默認(rèn)實(shí)現(xiàn)了NSCopying,并且一些具備可變類(lèi)型的類(lèi)如NSString英妓、NSArray挽放、NSDictionary,以及它們的可變類(lèi)型類(lèi)NSMutableString蔓纠、NSMutableArray和NSMutableDictionary也實(shí)現(xiàn)了NSMutableCopying辑畦。(查了大部分常用類(lèi),均實(shí)現(xiàn)了NSCopying腿倚,所以暫時(shí)這么說(shuō)吧航闺,可能有人說(shuō)NSNumber并沒(méi)有實(shí)現(xiàn)NSCopying,那你可以看一下它的父類(lèi)NSValue猴誊,其實(shí)現(xiàn)了NSCopying)
對(duì)于一些自定義類(lèi)潦刃,需要自己實(shí)現(xiàn)NSCopying。具體方式且看下部分懈叹。
三乖杠、非容器對(duì)象的深淺copy
首先,我們談一下非容器對(duì)象的深淺copy澄成,這些非容器對(duì)象胧洒,包含常用的NSString、NSNumber等墨状,也包括我們自定義的一些非容器類(lèi)的實(shí)例卫漫。下面分三個(gè)三面進(jìn)行分析。
1肾砂、首先說(shuō)說(shuō)深淺copy
準(zhǔn)則
淺copy:指針復(fù)制列赎,不會(huì)創(chuàng)建一個(gè)新的對(duì)象。
深copy:內(nèi)容復(fù)制镐确,會(huì)創(chuàng)建一個(gè)新的對(duì)象包吝。
2饼煞、框架類(lèi)的深淺copy
準(zhǔn)則
探究框架類(lèi)深copy還是淺copy,需要清楚的是該類(lèi)如何實(shí)現(xiàn)的NSCopying和NSMutableCopy的兩個(gè)方法copyWithZone:和mutableCopyWithZone:诗越。然而OC并不開(kāi)源砖瞧,并且本文這里也不會(huì)進(jìn)行源碼的推測(cè)。
那么嚷狞,我們應(yīng)該遵循怎樣一個(gè)原則呢块促?如下:
對(duì)immutableObject,即不可變對(duì)象床未,執(zhí)行copy褂乍,會(huì)得到不可變對(duì)象,并且是淺copy即硼。
對(duì)immutableObject逃片,即不可變對(duì)象,執(zhí)行mutableCopy只酥,會(huì)得到可變對(duì)象褥实,并且是深copy。
對(duì)mutableObject裂允,即可變對(duì)象损离,執(zhí)行copy,會(huì)得到不可變對(duì)象绝编,并且是深copy僻澎。
對(duì)mutableObject,即可變對(duì)象十饥,執(zhí)行mutableCopy窟勃,會(huì)得到可變對(duì)象,并且是深copy逗堵。
代碼
// 此處以NSString為例探究框架類(lèi)深淺copy
// 不可變對(duì)象
NSString *str = @"1";
NSString *str1 = [str copy];
NSString *str2 = [str mutableCopy];
// 可變對(duì)象
NSMutableString *mutableStr = [NSMutableString stringWithString:@"1"];
NSMutableString *mutableStr1 = [mutableStr copy];
NSMutableString *mutableStr2 = [mutableStr mutableCopy];
// 打印對(duì)象的指針來(lái)確認(rèn)是否創(chuàng)建了一個(gè)新的對(duì)象
// 不可變對(duì)象原始指針
NSLog(@"%p", str);
// 不可變對(duì)象copy后指針
NSLog(@"%p", str1);
// 不可變對(duì)象mutalbeCopy后指針
NSLog(@"%p", str2);
// 可變對(duì)象原始指針
NSLog(@"%p", mutableStr);
// 可變對(duì)象copy后指針
NSLog(@"%p", mutableStr1);
// 可變對(duì)象mutalbeCopy后指針
NSLog(@"%p", mutableStr2);
結(jié)果分析
// 此處依次對(duì)應(yīng)上述6個(gè)log秉氧,可見(jiàn)與前面所講的原則吻合(此處不驗(yàn)證可變類(lèi)型和不可變類(lèi)型,默認(rèn)上述原則正確即可)蜒秤。
2016-10-21 10:50:52.879 Memory[67680:5623387] 0x10d85a1b0
2016-10-21 10:50:52.879 Memory[67680:5623387] 0x10d85a1b0
2016-10-21 10:50:52.879 Memory[67680:5623387] 0x60800007a080
2016-10-21 10:50:52.879 Memory[67680:5623387] 0x60800007a9c0
2016-10-21 10:50:52.880 Memory[67680:5623387] 0xa000000000000311
2016-10-21 10:50:52.880 Memory[67680:5623387] 0x60800007a900
3汁咏、自定義類(lèi)的深淺copy
準(zhǔn)則
對(duì)于一個(gè)我們自定義的類(lèi)型,顯然比框架類(lèi)容易操縱的多作媚。此處就拿NSCopying舉例(因?yàn)閺臎](méi)有自定義過(guò)具有可變類(lèi)型的類(lèi)攘滩,當(dāng)然,如果有需要的話(huà)纸泡,也可以實(shí)現(xiàn)NSMutableCopying)漂问。自定義的類(lèi)就和2中的原則沒(méi)有半毛錢(qián)關(guān)系了,一切就看你怎么實(shí)現(xiàn)NSCopying協(xié)議中的copyWithZone:方法。
代碼
// Model定義级解,copyWithZone第一種實(shí)現(xiàn)(淺copy)
@interface Model1 : NSObject
@property (nonatomic, assign) NSInteger a;
@end
@implementation Model1
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
@end
// Model定義冒黑,copyWithZone第二種實(shí)現(xiàn)(深copy)
@interface Model1 : NSObject
@property (nonatomic, assign) NSInteger a;
@end
@implementation Model1
- (id)copyWithZone:(NSZone *)zone
{
Model1 *model = [[Model1 allocWithZone:zone] init];
model.a = self.a;
return model;
}
@end
// 分別選擇上述兩種model進(jìn)行指針打印田绑。
Model1 *model = [[Model1 alloc] init];
Model1 *copyModel = [model copy];
NSLog(@"%p", model);
NSLog(@"%p", copyModel);
結(jié)果分析
// 對(duì)應(yīng)上述一勤哗,可見(jiàn)實(shí)現(xiàn)了淺copy
2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0
2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0
// 對(duì)應(yīng)上述二,可見(jiàn)實(shí)現(xiàn)了深copy
2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001df00
2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001def0
四掩驱、容器對(duì)象的深淺copy
前文已經(jīng)知道了深淺copy的區(qū)別芒划,你也大致猜到了為什么將容器對(duì)象拿出來(lái)作為一塊。對(duì)欧穴,因?yàn)槿萜髦锌赡馨芏鄬?duì)象民逼,而這些對(duì)象也需要區(qū)分深淺copy。往深里說(shuō)涮帘,容器中可能包含容器對(duì)象拼苍,那更是麻煩了。不要急调缨,看下面疮鲫,以NSArray的深淺copy為例,將容器的深淺copy分為四種弦叶。
1俊犯、淺copy
準(zhǔn)則
容器的淺copy,符合三.2中的原則伤哺。
代碼
// 和NSString淺copy的驗(yàn)證步驟一樣
NSArray *arr = [NSArray arrayWithObjects:@"1", nil];
NSArray *copyArr = [arr copy];
NSLog(@"%p", arr);
NSLog(@"%p", copyArr);
結(jié)果分析
// 無(wú)疑是淺copy(你可能會(huì)問(wèn)燕侠,為什么不看一下arr和copyArr內(nèi)部元素的指針對(duì)比?這里并沒(méi)有必要立莉,最外層對(duì)象都沒(méi)有創(chuàng)建新的绢彤,里面不用驗(yàn)證)
2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690
2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690
2、單層深copy
準(zhǔn)則
容器的單層深copy蜓耻,符合三.2中的原則(只是深copy變成了單層深copy)杖虾。這里的單層指的是完成了NSArray對(duì)象的深copy,而未對(duì)其容器內(nèi)對(duì)象進(jìn)行處理媒熊。
代碼
NSArray *arr = [NSArray arrayWithObjects:@"1", nil];
NSArray *copyArr = [arr mutableCopy];
NSLog(@"%p", arr);
NSLog(@"%p", copyArr);
// 打印arr奇适、copyArr內(nèi)部元素進(jìn)行對(duì)比
NSLog(@"%p", arr[0]);
NSLog(@"%p", copyArr[0]);
結(jié)果分析
// 可發(fā)現(xiàn)前兩項(xiàng)地址不同,即完成深copy芦鳍,但是后兩項(xiàng)相同嚷往,這代表容器內(nèi)部的元素并沒(méi)有完成深copy,所有稱(chēng)之為單層深copy
2016-10-21 11:32:27.157 Memory[67801:5649757] 0x6000000030d0
2016-10-21 11:32:27.157 Memory[67801:5649757] 0x600000242e50
2016-10-21 11:32:27.157 Memory[67801:5649757] 0x10dd811b0
2016-10-21 11:32:27.157 Memory[67801:5649757] 0x10dd811b0
3柠衅、雙層深copy
準(zhǔn)則
容器的雙層深copy已經(jīng)脫離了三.2中的原則皮仁。這里的雙層指的是完成了NSArray對(duì)象和NSArray容器內(nèi)對(duì)象的深copy(為什么不說(shuō)完全,是因?yàn)闊o(wú)法處理NSArray中還有一個(gè)NSArray這種情況)。
代碼
// 隨意創(chuàng)建一個(gè)NSMutableString對(duì)象
NSMutableString *mutableString = [NSMutableString stringWithString:@"1"];
// 隨意創(chuàng)建一個(gè)包涵NSMutableString的NSMutableArray對(duì)象
NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"];
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil];
// 將mutableString和mutableArr放入一個(gè)新的NSArray中
NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil];
// 通過(guò)官方文檔提供的方式創(chuàng)建copy
NSArray *testArrCopy = [[NSArray alloc] initWithArray:testArr copyItems:YES];
// testArr和testArrCopy指針對(duì)比
NSLog(@"%p", testArr);
NSLog(@"%p", testArrCopy);
// testArr和testArrCopy中元素指針對(duì)比
// mutableString對(duì)比
NSLog(@"%p", testArr[0]);
NSLog(@"%p", testArrCopy[0]);
// mutableArr對(duì)比
NSLog(@"%p", testArr[1]);
NSLog(@"%p", testArrCopy[1]);
// mutableArr中的元素對(duì)比贷祈,即mutalbeString1對(duì)比
NSLog(@"%p", testArr[1][0]);
NSLog(@"%p", testArrCopy[1][0]);
結(jié)果分析
// 這里可以發(fā)現(xiàn)趋急,copy后,只有mutableArr中的mutalbeString1指針地址沒(méi)有變化势誊。而testArr的指針和testArr中的mutableArr呜达、mutableString的指針地址均發(fā)生變化。所以稱(chēng)之為雙層深復(fù)制粟耻。
2016-10-21 12:03:15.549 Memory[67855:5668888] 0x60800003c7a0
2016-10-21 12:03:15.549 Memory[67855:5668888] 0x60800003c880
2016-10-21 12:03:15.549 Memory[67855:5668888] 0x608000260540
2016-10-21 12:03:15.550 Memory[67855:5668888] 0xa000000000000311
2016-10-21 12:03:15.550 Memory[67855:5668888] 0x60800005d610
2016-10-21 12:03:15.550 Memory[67855:5668888] 0x60800000d2e0
2016-10-21 12:03:15.550 Memory[67855:5668888] 0x608000260980
2016-10-21 12:03:15.550 Memory[67855:5668888] 0x608000260980
限制
initWithArray: copyItems:會(huì)使NSArray中元素均執(zhí)行copy方法查近。這也是我在testArr中放入NSMutableArray和NSMutableString的原因。如果我放入的是NSArray或者NSString挤忙,執(zhí)行copy后霜威,只會(huì)發(fā)生指針復(fù)制;如果我放入的是未實(shí)現(xiàn)NSCopying協(xié)議的對(duì)象册烈,調(diào)用這個(gè)方法甚至?xí)rash戈泼。這里,官方文檔的描述有誤赏僧。
4大猛、完全深copy
準(zhǔn)則
如果想完美的解決NSArray嵌套NSArray這種情形,可以使用歸檔次哈、解檔的方式胎署。
代碼
// 隨意創(chuàng)建一個(gè)NSMutableString對(duì)象
NSMutableString *mutableString = [NSMutableString stringWithString:@"1"];
// 隨意創(chuàng)建一個(gè)包涵NSMutableString的NSMutableArray對(duì)象
NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"];
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil];
// 將mutableString和mutableArr放入一個(gè)新的NSArray中
NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil];
// 通過(guò)歸檔、解檔方式創(chuàng)建copy
NSArray *testArrCopy = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:testArr]];;
// testArr和testArrCopy指針對(duì)比
NSLog(@"%p", testArr);
NSLog(@"%p", testArrCopy);
// testArr和testArrCopy中元素指針對(duì)比
// mutableString對(duì)比
NSLog(@"%p", testArr[0]);
NSLog(@"%p", testArrCopy[0]);
// mutableArr對(duì)比
NSLog(@"%p", testArr[1]);
NSLog(@"%p", testArrCopy[1]);
// mutableArr中的元素對(duì)比窑滞,即mutalbeString1對(duì)比
NSLog(@"%p", testArr[1][0]);
NSLog(@"%p", testArrCopy[1][0]);
結(jié)果分析
// 可見(jiàn)完成了完全深復(fù)制琼牧,testArr和testArrCopy中的元素,以及容器中容器的指針地址完全不同哀卫,所以完成了完全深復(fù)制巨坊。
2016-10-21 12:19:34.022 Memory[67887:5677318] 0x60800002db00
2016-10-21 12:19:34.022 Memory[67887:5677318] 0x60800002dc20
2016-10-21 12:19:34.022 Memory[67887:5677318] 0x608000260400
2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080002603c0
2016-10-21 12:19:34.023 Memory[67887:5677318] 0x608000051d90
2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080000521e0
2016-10-21 12:19:34.023 Memory[67887:5677318] 0x608000260600
2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080002606c0
限制
歸檔和解檔的前提是NSArray中所有的對(duì)象都實(shí)現(xiàn)了NSCoding協(xié)議。
五此改、拾遺
1趾撵、關(guān)鍵字copy
代碼與結(jié)果
// 首先分別給出copy和strong修飾的屬性,以NSString舉例
// 1共啃、strong
@property (nonatomic, strong) NSString *str;
// 2占调、copy
@property (nonatomic, copy) NSString *str;
// 分別對(duì)1和2執(zhí)行下述代碼
NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"123"];
self.str = mutableStr;
[mutableStr appendString:@"456"];
NSLog(@"%@", self.str);
NSLog(@"%p", self.str);
NSLog(@"%@", mutableStr);
NSLog(@"%p", mutableStr);
// 結(jié)果1
2016-10-21 14:08:46.657 Memory[68242:5714288] 123456
2016-10-21 14:08:46.657 Memory[68242:5714288] 0x608000071040
2016-10-21 14:08:46.657 Memory[68242:5714288] 123456
2016-10-21 14:08:46.657 Memory[68242:5714288] 0x608000071040
// 結(jié)果2
2016-10-21 14:11:16.879 Memory[68264:5716282] 123
2016-10-21 14:11:16.880 Memory[68264:5716282] 0xa000000003332313
2016-10-21 14:11:16.880 Memory[68264:5716282] 123456
2016-10-21 14:11:16.880 Memory[68264:5716282] 0x60000007bbc0
分析
結(jié)果1為strong修飾的結(jié)果,可見(jiàn) [mutableStr appendString:@"456"]修改mutableStr造成了self.str的改變移剪,顯然不安全究珊;結(jié)果2為copy修飾的結(jié)果,可見(jiàn) [mutableStr appendString:@"456"]修改mutableStr未造成self.str的改變纵苛,顯然安全剿涮。(從內(nèi)存地址的變化也可以看出來(lái))
這里可以推測(cè)出言津,copy關(guān)鍵字是在str屬性的set方法里面返回了mutableStr的copy,而strong關(guān)鍵字僅僅是返回了mutableStr取试。
2悬槽、深淺copy對(duì)引用計(jì)數(shù)的影響
淺copy,類(lèi)似strong瞬浓,持有原始對(duì)象的指針初婆,會(huì)使retainCount加一。
深copy瑟蜈,會(huì)創(chuàng)建一個(gè)新的對(duì)象烟逊,不會(huì)對(duì)原始對(duì)象的retainCount變化渣窜。
// 也許你會(huì)疑問(wèn)arc下如何訪問(wèn)retainCount屬性铺根,這里提供了兩種方式(下面代碼中a代表一個(gè)任意對(duì)象,這個(gè)對(duì)象最好不要是NSString和NSNumber乔宿,因?yàn)橛盟鼈冞M(jìn)行測(cè)試會(huì)出問(wèn)題)
// kvc方式
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)a));
// 橋接字方式
NSLog(@"Retain count %@", [a valueForKey:@"retainCount"]);
3位迂、可變和不可變
可變和不可變上文談的不是很多,因?yàn)楸疚恼J(rèn)為這完全與NSCopying和NSMutableCopying的實(shí)現(xiàn)息息相關(guān)详瑞。當(dāng)然掂林,對(duì)于框架類(lèi),我們可以簡(jiǎn)單的認(rèn)為坝橡,copy方法返回的就是不可變對(duì)象泻帮,mutableCopy返回的就是可變對(duì)象。如果是自定義的類(lèi)计寇,就看你怎么實(shí)現(xiàn)NSCopying和NSMutableCopying協(xié)議了锣杂。
4、copy和block
首先番宁,MRR時(shí)代用retain修飾block會(huì)產(chǎn)生崩潰元莫,因?yàn)樽鳛閷傩缘腷lock在初始化時(shí)是被存放在棧區(qū)或靜態(tài)區(qū)的,如果棧區(qū)的block內(nèi)調(diào)用外部變量蝶押,那么block無(wú)法保留其內(nèi)存踱蠢,在初始化的作用域內(nèi)使用并不會(huì)有什么影響,但一旦出了block的初始化作用域棋电,就會(huì)引起崩潰茎截。所有MRC中使用copy修飾,將block拷貝到堆上赶盔。
其次企锌,在ARC時(shí)代,因?yàn)锳RC自動(dòng)完成了對(duì)block的copy招刨,所以修飾block用copy和strong都無(wú)所謂霎俩。
5哀军、strong和shallowCopy
這個(gè)問(wèn)題困惑了很久,最后只能得出一個(gè)結(jié)論打却,淺copy和strong引用的區(qū)別僅僅是淺copy多執(zhí)行一步copyWithZone:方法杉适。