一禀横、從面向?qū)ο蟮絆bjective-C概覽copy
1辟汰、面向?qū)ο螅?/h3>
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計中记劈,對象的copy就是創(chuàng)建一個已經(jīng)存在的對象的copy。這種對象的創(chuàng)建的結(jié)果被稱為原始對象的copy徐绑。copy是很基礎的诫咱,但是也有其精巧的地方,并且可能造成巨大的消耗刃唤。有很多種方式可以copy對象隔心,最常用的就是copy構造器和克隆。copy經(jīng)常用于對象的修改尚胞、移動和保護硬霍。如果上述的幾種應用都不需要,持有原始對象的引用就足夠了笼裳,并不需要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兩個方法是被所有對象繼承的(有點小毛病拜轨,應該指所有繼承自NSObject的類)抽减,這兩個方法就是為copy準備的。其中橄碾,mutableCopy是為了創(chuàng)建原始對象的可變類型的copy卵沉。這兩個方法分別調(diào)用copyWithZone和mutableCopyWithZone兩個方法來進行copy。一個對象必須實現(xiàn)copyWithZone或者mutableCopyWithZone法牲,才能進行copy或者mutableCopy史汗。
那么,我們可以從以上獲取到什么信息?
copy經(jīng)常用于對象的修改拒垃、移動和保護停撞。如果上述的幾種應用都不需要,持有原始對象的引用就足夠了悼瓮,并不需要copy戈毒。
一個類必須實現(xiàn)copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy横堡。
下一階段副硅,本文將展開講述OC中的copy相關信息以及如何使用copy方法。
二翅萤、Objective-C中copy相關
1、OC中的copy相關內(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;
修飾屬性的關鍵字copy
2套么、這里需要注意的有以下幾點
若想使用copy和mutableCopy,需要分別實現(xiàn)NSCopying協(xié)議和NSMutableCopying協(xié)議碳蛋,即實現(xiàn)copyWithZone:和mutableCopyWithZone:方法胚泌。
繼承自NSObject的大部分框架類均默認實現(xiàn)了NSCopying,并且一些具備可變類型的類如NSString肃弟、NSArray玷室、NSDictionary,以及它們的可變類型類NSMutableString笤受、NSMutableArray和NSMutableDictionary也實現(xiàn)了NSMutableCopying穷缤。(查了大部分常用類,均實現(xiàn)了NSCopying箩兽,所以暫時這么說吧津肛,可能有人說NSNumber并沒有實現(xiàn)NSCopying,那你可以看一下它的父類NSValue汗贫,其實現(xiàn)了NSCopying)
對于一些自定義類身坐,需要自己實現(xiàn)NSCopying秸脱。具體方式且看下部分。
三部蛇、非容器對象的深淺copy
首先摊唇,我們談一下非容器對象的深淺copy,這些非容器對象涯鲁,包含常用的NSString巷查、NSNumber等,也包括我們自定義的一些非容器類的實例撮竿。下面分三個三面進行分析吮便。
1、首先說說深淺copy
準則
淺copy:指針復制幢踏,不會創(chuàng)建一個新的對象髓需。
深copy:內(nèi)容復制,會創(chuàng)建一個新的對象房蝉。
此處僚匆,不進行過多的解釋,從下面的結(jié)果分析中搭幻,按例子來理解咧擂。
2、框架類的深淺copy
準則
探究框架類深copy還是淺copy檀蹋,需要清楚的是該類如何實現(xiàn)的NSCopying和NSMutableCopy的兩個方法copyWithZone:和mutableCopyWithZone:松申。然而OC并不開源,并且本文這里也不會進行源碼的推測俯逾。那么贸桶,我們應該遵循怎樣一個原則呢?如下:
對immutableObject桌肴,即不可變對象皇筛,執(zhí)行copy,會得到不可變對象坠七,并且是淺copy水醋。
對immutableObject,即不可變對象彪置,執(zhí)行mutableCopy拄踪,會得到可變對象,并且是深copy拳魁。
對mutableObject宫蛆,即可變對象,執(zhí)行copy,會得到不可變對象耀盗,并且是深copy想虎。
對mutableObject,即可變對象叛拷,執(zhí)行mutableCopy舌厨,會得到可變對象,并且是深copy忿薇。
代碼
// 此處以NSString為例探究框架類深淺copy
// 不可變對象
NSString *str = @"1";
NSString *str1 = [str copy];
NSString *str2 = [str mutableCopy];
// 可變對象
NSMutableString *mutableStr = [NSMutableString stringWithString:@"1"];
NSMutableString *mutableStr1 = [mutableStr copy];NSMutableString *mutableStr2 = [mutableStr mutableCopy];
// 打印對象的指針來確認是否創(chuàng)建了一個新的對象// 不可變對象原始指針NSLog(@"%p", str);
// 不可變對象copy后指針
NSLog(@"%p", str1);
// 不可變對象mutalbeCopy后指針
NSLog(@"%p", str2);
// 可變對象原始指針
NSLog(@"%p", mutableStr);
// 可變對象copy后指針
NSLog(@"%p", mutableStr1);
// 可變對象mutalbeCopy后指針
NSLog(@"%p", mutableStr2);
結(jié)果分析
// 此處依次對應上述6個log裙椭,可見與前面所講的原則吻合(此處不驗證可變類型和不可變類型,默認上述原則正確即可)署浩。
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
準則對于一個我們自定義的類型,顯然比框架類容易操縱的多筋栋。此處就拿NSCopying舉例(因為從沒有自定義過具有可變類型的類炊汤,當然,如果有需要的話弊攘,也可以實現(xiàn)NSMutableCopying)抢腐。自定義的類就和2中的原則沒有半毛錢關系了,一切就看你怎么實現(xiàn)NSCopying協(xié)議中的copyWithZone:方法襟交。
代碼
// Model定義迈倍,copyWithZone第一種實現(xiàn)(淺copy)
@interface Model1 : NSObject <NSCopying>
@property (nonatomic, assign) NSInteger a;
@end
@implementation Model1
- (id)copyWithZone:(NSZone *)zone { return self;}
@end
// Model定義,copyWithZone第二種實現(xiàn)(深copy)
@interface Model1 : NSObject <NSCopying>
@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進行指針打印捣域。
Model1 *model = [[Model1 alloc] init];
Model1 *copyModel = [model copy];
NSLog(@"%p", model);
NSLog(@"%p", copyModel);
結(jié)果分析
// 對應上述一啼染,可見實現(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
// 對應上述二,可見實現(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
3焕梅、雙層深copy
準則
容器的雙層深copy已經(jīng)脫離了三.2中的原則提完。這里的雙層指的是完成了NSArray對象和NSArray容器內(nèi)對象的深copy(為什么不說完全,是因為無法處理NSArray中還有一個NSArray這種情況)丘侠。
代碼
// 隨意創(chuàng)建一個NSMutableString對象
NSMutableString *mutableString = [NSMutableString stringWithString:@"1"];
// 隨意創(chuàng)建一個包涵NSMutableString的NSMutableArray對象
NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"];
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil];
// 將mutableString和mutableArr放入一個新的NSArray中
NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil];
// 通過官方文檔提供的方式創(chuàng)建copy
NSArray *testArrCopy = [[NSArray alloc] initWithArray:testArr copyItems:YES];
// testArr和testArrCopy指針對比
NSLog(@"%p", testArr);
NSLog(@"%p", testArrCopy);
// testArr和testArrCopy中元素指針對比
// mutableString對比
NSLog(@"%p", testArr[0]);
NSLog(@"%p", testArrCopy[0]);
// mutableArr對比
NSLog(@"%p", testArr[1]);
NSLog(@"%p", testArrCopy[1]);
// mutableArr中的元素對比,即mutalbeString1對比
NSLog(@"%p", testArr[1][0]);
NSLog(@"%p", testArrCopy[1][0]);
結(jié)果分析
// 這里可以發(fā)現(xiàn)逐样,copy后蜗字,只有mutableArr中的mutalbeString1指針地址沒有變化。而testArr的指針和testArr中的mutableArr脂新、mutableString的指針地址均發(fā)生變化挪捕。所以稱之為雙層深復制。
// 這里可以發(fā)現(xiàn)争便,copy后级零,只有mutableArr中的mutalbeString1指針地址沒有變化。而testArr的指針和testArr中的mutableArr、mutableString的指針地址均發(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:會使NSArray中元素均執(zhí)行copy方法。這也是我在testArr中放入NSMutableArray和NSMutableString的原因序调。如果我放入的是NSArray或者NSString醉锅,執(zhí)行copy后,只會發(fā)生指針復制发绢;如果我放入的是未實現(xiàn)NSCopying協(xié)議的對象硬耍,調(diào)用這個方法甚至會crash。
4边酒、完全深copy
準則
如果想完美的解決NSArray嵌套NSArray這種情形经柴,可以使用歸檔、解檔的方式墩朦。
代碼
// 隨意創(chuàng)建一個NSMutableString對象
NSMutableString *mutableString = [NSMutableString stringWithString:@"1"];
// 隨意創(chuàng)建一個包涵NSMutableString的NSMutableArray對象
NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"];
NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil];
// 將mutableString和mutableArr放入一個新的NSArray中
NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil];
// 通過歸檔坯认、解檔方式創(chuàng)建copy
NSArray *testArrCopy = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:testArr]];;
// testArr和testArrCopy指針對比
NSLog(@"%p", testArr);
NSLog(@"%p", testArrCopy);
// testArr和testArrCopy中元素指針對比
// mutableString對比
NSLog(@"%p", testArr[0]);
NSLog(@"%p", testArrCopy[0]);
// mutableArr對比
NSLog(@"%p", testArr[1]);
NSLog(@"%p", testArrCopy[1]);
// mutableArr中的元素對比,即mutalbeString1對比
NSLog(@"%p", testArr[1][0]);
NSLog(@"%p", testArrCopy[1][0]);
結(jié)果分析
// 可見完成了完全深復制介杆,testArr和testArrCopy中的元素鹃操,以及容器中容器的指針地址完全不同,所以完成了完全深復制春哨。
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中所有的對象都實現(xiàn)了NSCoding協(xié)議荆隘。
五、拾遺
1赴背、關鍵字copy
代碼與結(jié)果
// 首先分別給出copy和strong修飾的屬性椰拒,以NSString舉例
// 1、strong
@property (nonatomic, strong) NSString *str;
// 2凰荚、copy
@property (nonatomic, copy) NSString *str;
// 分別對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é)果分析
// 結(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é)果燃观,可見** [mutableStr appendString:@"456"]**修改mutableStr造成了self.str的改變,顯然不安全便瑟;結(jié)果2為copy修飾的結(jié)果缆毁,可見 [mutableStr appendString:@"456"]修改mutableStr未造成self.str的改變,顯然安全到涂。(從內(nèi)存地址的變化也可以看出來)
這里可以推測出脊框,copy關鍵字是在str屬性的set方法里面返回了mutableStr的copy,而strong關鍵字僅僅是返回了mutableStr践啄。
2浇雹、深淺copy對引用計數(shù)的影響
淺copy,類似strong屿讽,持有原始對象的指針昭灵,會使retainCount加一。
深copy,會創(chuàng)建一個新的對象烂完,不會對原始對象的retainCount變化试疙。
3、可變和不可變
可變和不可變上文談的不是很多窜护,因為本文認為這完全與NSCopying和NSMutableCopying的實現(xiàn)息息相關效斑。當然,對于框架類柱徙,我們可以簡單的認為缓屠,copy方法返回的就是不可變對象,mutableCopy返回的就是可變對象护侮。如果是自定義的類敌完,就看你怎么實現(xiàn)NSCopying和NSMutableCopying協(xié)議了。
4羊初、copy和block
首先滨溉,MRC時代用retain修飾block會產(chǎn)生崩潰,因為作為屬性的block在初始化時是被存放在棧區(qū)或靜態(tài)區(qū)的长赞,如果棧區(qū)的block內(nèi)調(diào)用外部變量晦攒,那么block無法保留其內(nèi)存,在初始化的作用域內(nèi)使用并不會有什么影響得哆,但一旦出了block的初始化作用域脯颜,就會引起崩潰。所有MRC中使用copy修飾贩据,將block拷貝到堆上栋操。其次,在ARC時代饱亮,因為ARC自動完成了對block的copy矾芙,所以修飾block用copy和strong都無所謂。
5近上、strong和shallowCopy
這個問題困惑了很久剔宪,最后只能得出一個結(jié)論,淺copy和strong引用的區(qū)別僅僅是淺copy多執(zhí)行一步copyWithZone:方法壹无。