Objective-C copy(轉)

1、從面向對象到Objective-C概覽copy

一、面向對象:

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.html

在面向對象的程序設計中,對象的copy就是建立一個已經存在的對象的copy。這種對象的建立的結果被稱爲原始對象的copy。copy是很基礎的,可是也有其精巧的地方,而且可能形成巨大的消耗。有不少種方式能夠copy對象,最經常使用的就是copy構造器和克隆。copy常常用於對象的修改、移動和保護。若是上述的幾種應用都不須要,持有原始對象的引用就足夠了,並不須要copy。安全

二、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.app

在OC中,copy和mutableCopy兩個方法是被全部對象繼承的(有點小毛病,應該指全部繼承自NSObject的類),這兩個方法就是爲copy準備的。其中,mutableCopy是爲了建立原始對象的可變類型的copy。這兩個方法分別調用copyWithZone和mutableCopyWithZone兩個方法來進行copy。一個對象必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。框架

那麼,咱們能夠從以上獲取到什麼信息?測試

  • copy常常用於對象的修改、移動和保護。若是上述的幾種應用都不須要,持有原始對象的引用就足夠了,並不須要copy。
  • 一個類必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。

下一階段,本文將展開講述OC中的copy相關信息以及如何使用copy方法。ui

2、Objective-C中copy相關

一、OC中的copy相關內容

  • 在XCode 裏Foundation.framework下的Headers裏,也在系統裏找到原文件:/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

二、這裏須要注意的有如下幾點

  • 若想使用copymutableCopy,須要分別實現NSCopying協議和NSMutableCopying協議,即實現copyWithZone:mutableCopyWithZone:方法。
  • 繼承自NSObject的大部分框架類均默認實現了NSCopying,而且一些具有可變類型的類如NSString、NSArray、NSDictionary,以及它們的可變類型類NSMutableString、NSMutableArray和NSMutableDictionary也實現了NSMutableCopying。(查了大部分經常使用類,均實現了NSCopying,因此暫時這麼說吧,可能有人說NSNumber並無實現NSCopying,那你能夠看一下它的父類NSValue,其實現了NSCopying)
  • 對於一些自定義類,須要本身實現NSCopying。具體方式且看下部分。

3、非容器對象的深淺copy

首先,咱們談一下非容器對象的深淺copy,這些非容器對象,包含經常使用的NSString、NSNumber等,也包括咱們自定義的一些非容器類的實例。下面分三個三面進行分析。atom

一、首先說說深淺copy

準則
淺copy:指針複製,不會建立一個新的對象。
深copy:內容複製,會建立一個新的對象。spa

此處,不進行過多的解釋,從下面的結果分析中,按例子來理解。設計

二、框架類的深淺copy

準則
探究框架類深copy仍是淺copy,須要清楚的是該類如何實現的NSCopying和NSMutableCopy的兩個方法copyWithZone:和mutableCopyWithZone:。然而OC並不開源,而且本文這裏也不會進行源碼的推測。
那麼,咱們應該遵循怎樣一個原則呢?以下:指針

  • 對immutableObject,即不可變對象,執行copy,會獲得不可變對象,而且是淺copy。
  • 對immutableObject,即不可變對象,執行mutableCopy,會獲得可變對象,而且是深copy。
  • 對mutableObject,便可變對象,執行copy,會獲得不可變對象,而且是深copy。
  • 對mutableObject,便可變對象,執行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]; // 打印對象的指針來確認是否建立了一個新的對象 // 不可變對象原始指針 NSLog(@"%p", str); // 不可變對象copy後指針 NSLog(@"%p", str1); // 不可變對象mutalbeCopy後指針 NSLog(@"%p", str2); // 可變對象原始指針 NSLog(@"%p", mutableStr); // 可變對象copy後指針 NSLog(@"%p", mutableStr1); // 可變對象mutalbeCopy後指針 NSLog(@"%p", mutableStr2);

結果分析

// 此處依次對應上述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

三、自定義類的深淺copy

準則
對於一個咱們自定義的類型,顯然比框架類容易操縱的多。此處就拿NSCopying舉例(由於從沒有自定義過具備可變類型的類,固然,若是有須要的話,也能夠實現NSMutableCopying)。自定義的類就和2中的原則沒有半毛錢關係了,一切就看你怎麼實現NSCopying協議中的copyWithZone:方法。

代碼

// Model定義,copyWithZone第一種實現(淺copy) @interface Model1 : NSObject <NSCopying> @property (nonatomic, assign) NSInteger a; @end @implementation Model1 - (id)copyWithZone:(NSZone *)zone { return self; } @end // Model定義,copyWithZone第二種實現(深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);

結果分析

// 對應上述一,可見實現了淺copy 2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0 2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0 // 對應上述二,可見實現了深copy 2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001df00 2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001def0

4、容器對象的深淺copy

前文已經知道了深淺copy的區別,你也大體猜到了爲何將容器對象拿出來做爲一塊。對,由於容器中可能包含不少對象,而這些對象也須要區分深淺copy。往深裏說,容器中可能包含容器對象,那更是麻煩了。不要急,看下面,以NSArray的深淺copy爲例,將容器的深淺copy分爲四種。

一、淺copy

準則
容器的淺copy,符合三.2中的原則。
代碼

// 和NSString淺copy的驗證步驟同樣 NSArray *arr = [NSArray arrayWithObjects:@"1", nil]; NSArray *copyArr = [arr copy]; NSLog(@"%p", arr); NSLog(@"%p", copyArr);

結果分析

// 無疑是淺copy(你可能會問,爲何不看一下arr和copyArr內部元素的指針對比?這裏並無必要,最外層對象都沒有建立新的,裏面不用驗證) 2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690 2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690

二、單層深copy

準則
容器的單層深copy,符合三.2中的原則(只是深copy變成了單層深copy)。這裏的單層指的是完成了NSArray對象的深copy,而未對其容器內對象進行處理。
代碼

NSArray *arr = [NSArray arrayWithObjects:@"1", nil]; NSArray *copyArr = [arr mutableCopy]; NSLog(@"%p", arr); NSLog(@"%p", copyArr); // 打印arr、copyArr內部元素進行對比 NSLog(@"%p", arr[0]); NSLog(@"%p", copyArr[0]);

結果分析

// 可發現前兩項地址不一樣,即完成深copy,可是後兩項相同,這表明容器內部的元素並無完成深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

三、雙層深copy

準則
容器的雙層深copy已經脫離了三.2中的原則。這裏的雙層指的是完成了NSArray對象和NSArray容器內對象的深copy(爲何不說徹底,是由於沒法處理NSArray中還有一個NSArray這種狀況)。
代碼

// 隨意建立一個NSMutableString對象 NSMutableString *mutableString = [NSMutableString stringWithString:@"1"]; // 隨意建立一個包涵NSMutableString的NSMutableArray對象 NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"]; NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil]; // 將mutableString和mutableArr放入一個新的NSArray中 NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil]; // 經過官方文檔提供的方式建立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]);

結果分析

// 這裏能夠發現,copy後,只有mutableArr中的mutalbeString1指針地址沒有變化。而testArr的指針和testArr中的mutableArr、mutableString的指針地址均發生變化。因此稱之爲雙層深複製。 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中元素均執行copy方法。這也是我在testArr中放入NSMutableArray和NSMutableString的緣由。若是我放入的是NSArray或者NSString,執行copy後,只會發生指針複製;若是我放入的是未實現NSCopying協議的對象,調用這個方法甚至會crash。這裏,官方文檔的描述有誤。

If the objects in the collection have adopted the NSCopying
protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects.

四、徹底深copy

準則
若是想完美的解決NSArray嵌套NSArray這種情形,可使用歸檔、解檔的方式。
代碼

// 隨意建立一個NSMutableString對象 NSMutableString *mutableString = [NSMutableString stringWithString:@"1"]; // 隨意建立一個包涵NSMutableString的NSMutableArray對象 NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"]; NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil]; // 將mutableString和mutableArr放入一個新的NSArray中 NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil]; // 經過歸檔、解檔方式建立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]);

結果分析

// 可見完成了徹底深複製,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中全部的對象都實現了NSCoding協議。

5、拾遺

一、關鍵字copy

代碼與結果

// 首先分別給出copy和strong修飾的屬性,以NSString舉例 // 一、strong @property (nonatomic, strong) NSString *str; // 二、copy @property (nonatomic, copy) NSString *str; // 分別對1和2執行下述代碼 NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"123"]; self.str = mutableStr; [mutableStr appendString:@"456"]; NSLog(@"%@", self.str); NSLog(@"%p", self.str); NSLog(@"%@", mutableStr); NSLog(@"%p", mutableStr); // 結果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 // 結果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

分析

  • 結果1爲strong修飾的結果,可見 [mutableStr appendString:@"456"]修改mutableStr形成了self.str的改變,顯然不安全;結果2爲copy修飾的結果,可見 [mutableStr appendString:@"456"]修改mutableStr未形成self.str的改變,顯然安全。(從內存地址的變化也能夠看出來)
  • 這裏能夠推測出,copy關鍵字是在str屬性的set方法裏面返回了mutableStr的copy,而strong關鍵字僅僅是返回了mutableStr。

二、深淺copy對引用計數的影響

淺copy,相似strong,持有原始對象的指針,會使retainCount加一。
深copy,會建立一個新的對象,不會對原始對象的retainCount變化。

// 也許你會疑問arc下如何訪問retainCount屬性,這裏提供了兩種方式(下面代碼中a表明一個任意對象,這個對象最好不要是NSString和NSNumber,由於用它們進行測試會出問題) // kvc方式 NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)a)); // 橋接字方式 NSLog(@"Retain count %@", [a valueForKey:@"retainCount"]);

三、可變和不可變

可變和不可變上文談的不是不少,由於本文認爲這徹底與NSCopying和NSMutableCopying的實現息息相關。固然,對於框架類,咱們能夠簡單的認爲,copy方法返回的就是不可變對象,mutableCopy返回的就是可變對象。若是是自定義的類,就看你怎麼實現NSCopying和NSMutableCopying協議了。

四、copy和block

首先,MRR時代用retain修飾block會產生崩潰,由於做爲屬性的block在初始化時是被存放在靜態區的,若是block內調用外部變量,那麼block沒法保留其內存,在初始化的做用域內使用並不會有什麼影響,但一旦出了block的初始化做用域,就會引發崩潰。全部MRC中使用copy修飾,將block拷貝到堆上。
其次,在ARC時代,由於ARC自動完成了對block的copy,因此修飾block用copy和strong都無所謂。

五、strong和shallowCopy

這個問題困惑了好久,最後只能得出一個結論,淺copy和strong引用的區別僅僅是淺copy多執行一步copyWithZone:方法。

6、文獻

一、https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW1
二、https://en.wikipedia.org/wiki/Object_copying

相關文章
相關標籤/搜索