Copy和MutableCopy

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

  • 面向對象:

在面向對象的程序設計中,對象的copy就是創建一個已經存在的對象的copy。這種對象的創建的結果被稱為原始對象的copy。copy是很基礎的,但是也有其精巧的地方,并且可能造成巨大的消耗。有很多種方式可以copy對象,最常用的就是copy構造器和克隆。copy經常用于對象的修改、移動和保護。如果上述的幾種應用都不需要,持有原始對象的引用就足夠了,并不需要copy。

  • OC:

在OC中,copy和mutableCopy兩個方法是被所有對象繼承的(有點小毛病,應該指所有繼承自NSObject的類),這兩個方法就是為copy準備的。其中,mutableCopy是為了創建原始對象的可變類型的copy。這兩個方法分別調用copyWithZone和mutableCopyWithZone兩個方法來進行copy。一個類必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。
那么,我們可以從以上獲取到什么信息?

copy經常用于對象的修改、移動和保護。如果上述的幾種應用都不需要,持有原始對象的引用就足夠了,并不需要copy。

一個類必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。

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

二、Objective-C中copy相關

1、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

2、這里需要注意的有以下幾點

  • 若想使用copy和mutableCopy,需要分別實現NSCopying協議和NSMutableCopying協議,即實現copyWithZone:和mutableCopyWithZone:方法。

  • 繼承自NSObject的大部分框架類均默認實現了NSCopying,并且一些具備可變類型的類如NSString、NSArray、NSDictionary,以及它們的可變類型類NSMutableString、NSMutableArray和NSMutableDictionary也實現了NSMutableCopying。(查了大部分常用類,均實現了NSCopying,所以暫時這么說吧,可能有人說NSNumber并沒有實現NSCopying,那你可以看一下它的父類NSValue,其實現了NSCopying)

  • 對于一些自定義類,需要自己實現NSCopying。具體方式且看下部分。

三、非容器對象的深淺copy

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

1、首先說說深淺copy

準則

淺copy:指針復制,不會創建一個新的對象。

深copy:內容復制,會創建一個新的對象。

2、框架類的深淺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

3、自定義類的深淺copy

準則

對于一個我們自定義的類型,顯然比框架類容易操縱的多。此處就拿NSCopying舉例(因為從沒有自定義過具有可變類型的類,當然,如果有需要的話,也可以實現NSMutableCopying)。自定義的類就和2中的原則沒有半毛錢關系了,一切就看你怎么實現NSCopying協議中的copyWithZone:方法。
代碼

// Model定義,copyWithZone第一種實現(淺copy)

@interface Model1 : NSObject
@property (nonatomic, assign) NSInteger a;
@end

@implementation Model1
- (id)copyWithZone:(NSZone *)zone 
{    
    return self;
}
@end

// Model定義,copyWithZone第二種實現(深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進行指針打印。

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

四、容器對象的深淺copy

前文已經知道了深淺copy的區別,你也大致猜到了為什么將容器對象拿出來作為一塊。對,因為容器中可能包含很多對象,而這些對象也需要區分深淺copy。往深里說,容器中可能包含容器對象,那更是麻煩了。不要急,看下面,以NSArray的深淺copy為例,將容器的深淺copy分為四種。

1、淺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

2、單層深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

3、雙層深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。這里,官方文檔的描述有誤。

4、完全深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協議。

五、拾遺

1、關鍵字copy

代碼與結果

// 首先分別給出copy和strong修飾的屬性,以NSString舉例

// 1、strong

@property (nonatomic, strong) NSString *str;

// 2、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。

2、深淺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"]);

3、可變和不可變

可變和不可變上文談的不是很多,因為本文認為這完全與NSCopying和NSMutableCopying的實現息息相關。當然,對于框架類,我們可以簡單的認為,copy方法返回的就是不可變對象,mutableCopy返回的就是可變對象。如果是自定義的類,就看你怎么實現NSCopying和NSMutableCopying協議了。

4、copy和block

首先,MRR時代用retain修飾block會產生崩潰,因為作為屬性的block在初始化時是被存放在棧區或靜態區的,如果棧區的block內調用外部變量,那么block無法保留其內存,在初始化的作用域內使用并不會有什么影響,但一旦出了block的初始化作用域,就會引起崩潰。所有MRC中使用copy修飾,將block拷貝到堆上。

其次,在ARC時代,因為ARC自動完成了對block的copy,所以修飾block用copy和strong都無所謂。

5、strong和shallowCopy

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容