一、從面向?qū)ο蟮絆bjective-C概覽copy
1、面向?qū)ο螅?/p>
In object-oriented programming, object copying is creating a copy of an existing object, a unit of data in object-oriented programming. The resulting object is called an object copy or simply copy of the original object. Copying is basic but has subtleties and can have significant overhead. There are several ways to copy an object, most commonly by a copy constructor or cloning. Copying is done mostly so the copy can be modified or moved, or the current value preserved. If either of these is unneeded, a reference to the original data is sufficient and more efficient, as no copying occurs.
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,對(duì)象的copy就是創(chuàng)建一個(gè)已經(jīng)存在的對(duì)象的copy。這種對(duì)象的創(chuàng)建的結(jié)果被稱為原始對(duì)象的copy。copy是很基礎(chǔ)的,但是也有其精巧的地方,并且可能造成巨大的消耗。有很多種方式可以copy對(duì)象,最常用的就是copy構(gòu)造器和克隆。copy經(jīng)常用于對(duì)象的修改、移動(dòng)和保護(hù)。如果上述的幾種應(yīng)用都不需要,持有原始對(duì)象的引用就足夠了,并不需要copy。
2、OC:
In Objective-C, the methods copy and mutableCopy are inherited by all objects and intended for performing copies; the latter is for creating a mutable type of the original object. These methods in turn call the copyWithZone and mutableCopyWithZone methods, respectively, to perform the copying. An object must implement the corresponding copyWithZone method to be copyable.
在OC中,copy和mutableCopy兩個(gè)方法是被所有對(duì)象繼承的(有點(diǎn)小毛病,應(yīng)該指所有繼承自NSObject的類),這兩個(gè)方法就是為copy準(zhǔn)備的。其中,mutableCopy是為了創(chuàng)建原始對(duì)象的可變類型的copy。這兩個(gè)方法分別調(diào)用copyWithZone和mutableCopyWithZone兩個(gè)方法來(lái)進(jìn)行copy。一個(gè)對(duì)象必須實(shí)現(xiàn)copyWithZone或者mutableCopyWithZone,才能進(jìn)行copy或者mutableCopy。
那么,我們可以從以上獲取到什么信息?
copy經(jīng)常用于對(duì)象的修改、移動(dòng)和保護(hù)。如果上述的幾種應(yīng)用都不需要,持有原始對(duì)象的引用就足夠了,并不需要copy。
一個(gè)類必須實(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
6@protocol?NSCopying
-?(id)copyWithZone:(nullable?NSZone?*)zone;
@end
@protocol?NSMutableCopying
-?(id)mutableCopyWithZone:(nullable?NSZone?*)zone;
@end
在/usr/include/objc 下面找到 runtime 的 NSObject.h
1
2-?(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的大部分框架類均默認(rèn)實(shí)現(xiàn)了NSCopying,并且一些具備可變類型的類如NSString、NSArray、NSDictionary,以及它們的可變類型類NSMutableString、NSMutableArray和NSMutableDictionary也實(shí)現(xiàn)了NSMutableCopying。(查了大部分常用類,均實(shí)現(xiàn)了NSCopying,所以暫時(shí)這么說(shuō)吧,可能有人說(shuō)NSNumber并沒(méi)有實(shí)現(xiàn)NSCopying,那你可以看一下它的父類NSValue,其實(shí)現(xiàn)了NSCopying)
對(duì)于一些自定義類,需要自己實(shí)現(xiàn)NSCopying。具體方式且看下部分。
三、非容器對(duì)象的深淺copy
首先,我們談一下非容器對(duì)象的深淺copy,這些非容器對(duì)象,包含常用的NSString、NSNumber等,也包括我們自定義的一些非容器類的實(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ì)象。
此處,不進(jìn)行過(guò)多的解釋,從下面的結(jié)果分析中,按例子來(lái)理解。
2、框架類的深淺copy
準(zhǔn)則
探究框架類深copy還是淺copy,需要清楚的是該類如何實(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為例探究框架類深淺copy
26//?此處以NSString為例探究框架類深淺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)證可變類型和不可變類型,默認(rèn)上述原則正確即可)。
7//?此處依次對(duì)應(yīng)上述6個(gè)log,可見(jiàn)與前面所講的原則吻合(此處不驗(yàn)證可變類型和不可變類型,默認(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、自定義類的深淺copy
準(zhǔn)則
對(duì)于一個(gè)我們自定義的類型,顯然比框架類容易操縱的多。此處就拿NSCopying舉例(因?yàn)閺臎](méi)有自定義過(guò)具有可變類型的類,當(dāng)然,如果有需要的話,也可以實(shí)現(xiàn)NSMutableCopying)。自定義的類就和2中的原則沒(méi)有半毛錢關(guān)系了,一切就看你怎么實(shí)現(xiàn)NSCopying協(xié)議中的copyWithZone:方法。
代碼
// Model定義,copyWithZone第一種實(shí)現(xiàn)(淺copy)
30//?Model定義,copyWithZone第一種實(shí)現(xiàn)(淺copy)
@interface?Model1?:?NSObject?
@property?(nonatomic,?assign)?NSInteger?a;
@end
@implementation?Model1
-?(id)copyWithZone:(NSZone?*)zone?{
returnself;
}
@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;
returnmodel;
}
@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)證步驟一樣
6//?和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];
9NSArray?*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,所有稱之為單層深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ā)生變化。所以稱之為雙層深復(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。這里,官方文檔的描述有誤。
If the objects in the collection have adopted theNSCopying
protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects.
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,類似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)題)
// 橋接字方式
NSLog(@"Retain?count?is?%ld",?CFGetRetainCount((__bridge?CFTypeRef)a));
// kvc方式
NSLog(@"Retain?count?%@",?[a?valueForKey:@"retainCount"]);
3、可變和不可變
可變和不可變上文談的不是很多,因?yàn)楸疚恼J(rèn)為這完全與NSCopying和NSMutableCopying的實(shí)現(xiàn)息息相關(guān)。當(dāng)然,對(duì)于框架類,我們可以簡(jiǎn)單的認(rèn)為,copy方法返回的就是不可變對(duì)象,mutableCopy返回的就是可變對(duì)象。如果是自定義的類,就看你怎么實(shí)現(xiàn)NSCopying和NSMutableCopying協(xié)議了。
4、copy和block
首先,MRC時(shí)代用retain修飾block會(huì)產(chǎn)生崩潰,因?yàn)樽鳛閷傩缘腷lock在初始化時(shí)是被存放在靜態(tài)區(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:方法。
六、文獻(xiàn)
2、https://en.wikipedia.org/wiki/Object_copying
------------摘自http://www.cocoachina.com/ios/20161116/18100.html
weak(_weak):主要是防止循環(huán)應(yīng)用,如果指向的內(nèi)存釋放后,會(huì)自動(dòng)nil化指針,防止野指針;
strong(_strong):指向新的對(duì)象時(shí),之前的對(duì)象會(huì)自動(dòng)釋放(只要一個(gè)對(duì)象沒(méi)有一個(gè)strong類型的指針指向時(shí),就會(huì)自動(dòng)釋放)