Copy And Block

本文為轉載:

作者:zyydeveloper

鏈接:http://www.lxweimin.com/p/5f776a4816ee

不敢說覆蓋OC中所有copy的知識點,但最起碼是目前最全的最新的一篇關于 copy的技術文檔了。后續發現有新的copy 知識點,我會第一時間更新到這篇博客內。

先提供一下完整的代碼鏈接下載完整代碼? OC_Copy后續博客中會用到。

本篇博客內容結構圖:

NSObjct對象賦值操作在內存中的表現

將這部分拿到開篇來說,是因為如果我們不能準確的區分"對象"和"指針"這2個概念,那么后面關于copy的討論和思考都是扯淡。

一行“千寫萬寫而不明"的代碼

我們來分析一下這行代碼

Person *p = [Person alloc] init];

Personp

這句話,僅僅是聲明了1個變量而已. 這個變量的類型是 Person

同時p是1個局部的變量, 所以p變量是存儲在棧區的

p和* 放到一起 表示 p變量 是一個 指針變量,既然是指針變量,那么p只能存儲一個內存地址

本質上來講 p是1個指針變量 不是1個對象, p 指向的是 某一塊內存區域的首地址

[Person alloc] init]

alloc 在堆區開辟一塊內存空間,用來存放Person類的一個具體對象。這個對象 運行后面的指針(可以是多個) 來指向它。來一個指針,指向它,且那個指針是 strong 修飾的, 那么 這個對象的引用計數就+1,否者一直是 1 (alloc時候產生的)

init 進行一些初始化操作(如果Person類 是被第一次訪問,會進行類加載,類加載詳細過程我們后續再談)

Person *p = [Person alloc] init]

中間這個 “=”? 符號? 將2段代碼連在一起, 充當一個橋梁作用:

第一: 允許 p 指針 指向這個對象占用的內存區域的首地址(可以簡潔的理解為? 允許p指針指向對象)

第二: 將這個對象地址賦值給p指針

第三:后續所有對 “對象“的操作,都可以通過對p指針進行間接操作來完成。

這里先介紹一個基礎,后續,我會拿出一篇博客 專門來說明 OC中的變量在內存中的使用分布。

一圖以蔽之

copy 和 mutableCopy

copy 就是淺拷貝,mutableCopy就是深拷貝?

不知道從何時起,流行起這樣一句話:copy 就是淺拷貝,mutableCopy就是深拷貝,這句話說的不準確,因為對可變對象調用copy 就是深拷貝,會產生新的對象。 不用代碼來求證的結論,永遠都是毫無意思的,下面我們會用代碼來驗證。

任何對象都可以執行 copy和mutableCopy? 看一段官方規定

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是為了創建原始對象的可變類型的copy。這兩個方法分別調用copyWithZone和mutableCopyWithZone兩個方法來進行copy。一個類必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。

shallow copy 和 deep copy

英文好的同學依然是直接點擊查看官方對于深淺copy的定義(傳送門)

英文不好的同學,就只能聽我嘮叨了,我們將官方文檔概述如下:

首先看張官方給的圖

明白了對象和指針的區別,在看這個圖就很容易理解了。

定義

對象拷貝有兩種方式:淺復制和深復制。顧名思義,淺復制,并不拷貝對象本身,僅僅是拷貝指向對象的指針;深復制是除了拷貝指向對象的指針,而且直接拷貝整個對象內存到另一塊內存中。再簡單些說:淺復制就是指針拷貝;深復制就是內容拷貝。

準則

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

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

對引用計數的影響

淺copy,類似strong,持有原始對象的指針,會使retainCount加一。淺copy和strong引用的區別僅僅是淺copy多執行一步copyWithZone:方法。

深copy,會創建一個新的對象,不會對原始對象的retainCount變化。

關鍵點

找準源對象類型才能分清copy 是淺拷貝還是深拷貝

使用原則

針對不可變對象調用copy返回該對象本身,調用mutableCopy返回一個可變對象(新的);

針對可變對象調用copy返回一個不可變對象(新的),調用mutableCopy返回另外一個可變對象(新的)。

classcopymutableCopy

不可變(如,NSString)(淺拷貝) 返回本身(深拷貝)創建新的可變對象(如,創建一個NSMutableString對象,地址跟原對象不同)

可變(如,NSMutableString)(深拷貝) 創建新的不可變對象(如,創建一個NSTaggedPointerString對象,地址跟原對象不同 )(深拷貝) 創建新的可變對象(如,創建一個NSMutableString對象,地址跟原對象不同 )

明白了上述內容,我們結合代碼來看一下具體的shallow copy 和 deep copy

非集合類的對象 shallow copy 和 deep copy

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy1 工程,下載完整代碼? OC_Copy

準則

不管是集合類對象,還是非集合類對象,接收到copy和mutableCopy消息時,都遵循以下準則:

copy返回imutable對象;mutableCopy返回mutable對象;

對copy返回值使用mutable對象接口就會crash

系統非集合類對象指的是 NSString, NSNumber ... 之類的對象。下面先看個非集合類immutable對象拷貝的例子

NSString *string = @"origin";NSString *stringCopy = [stringcopy];NSMutableString *stringMCopy = [stringmutableCopy];

通過查看內存,可以看到 stringCopy 和 string 的地址是一樣,進行了指針拷貝;而 stringMCopy 的地址和 string 不一樣,進行了內容拷貝;

一圖以蔽之

再看mutable對象拷貝例子

NSMutableString *string = [NSMutableString stringWithString: @"origin"];//copyNSString *stringCopy = [stringcopy];NSMutableString *mStringCopy = [stringcopy];NSMutableString *stringMCopy = [stringmutableCopy];//change value[mStringCopy appendString:@"mm"];//這行代碼會crash[stringappendString:@" origion!"];[stringMCopy appendString:@"!!"];

運行以上代碼,會在第7行crash,原因就是 copy 返回的對象是 immutable 對象,違反了我們上述中的原則。 注釋第7行后再運行,查看內存,發現 string、stringCopy、stringMCopy 三個對象的內存地址都不一樣,說明此時都是做內容拷貝。

一圖以蔽之

綜上兩個例子,我們可以得出結論:

在非集合類對象中:

對immutable對象進行copy操作,是指針復制,mutableCopy操作時內容復制;

對mutable對象進行copy和mutableCopy都是內容復制。

用代碼簡單表示如下:

[immutableObject copy]? ? ? ? ? ? // 淺復制 + 不可變對象

[immutableObject mutableCopy] //深復制 + 可變對象

[mutableObject copy] //深復制 + 不可變對象

[mutableObject mutableCopy] //深復制 +? 可變對象

集合類的對象自身的 shallow copy 和 deep copy

我著重強調 “對象自身” 是因為這里對象 是集合類型的,它具有存儲其它對象的能力, 對象自身 和 對象內的元素 執行的 copy操作是不同的。這里我們先看“對象自身”,后面 我們會講 “對象內的元素” copy操作。

集合類對象是指NSArray、NSDictionary、NSSet ... 之類的對象。下面先看集合類immutable對象使用copy和mutableCopy的一個例子:

NSArray*array = @[@[@"a", @"b"], @[@"c", @"d"]];NSArray*copyArray = [array copy];NSMutableArray*mCopyArray = [array mutableCopy];

查看內容,可以看到copyArray和array的地址是一樣的,而mCopyArray和array的地址是不同的。說明copy操作進行了指針拷貝,mutableCopy進行了內容拷貝。但需要強調的是:此處的內容拷貝,僅僅是拷貝array這個對象,array集合內部的元素仍然是指針拷貝。

一圖以蔽之

這和上面的非集合immutable對象的拷貝還是挺相似的,那么mutable對象的拷貝會不會類似呢?我們繼續往下,看mutable對象拷貝的例子:

NSMutableArray*array = [NSMutableArrayarrayWithObjects:[NSMutableStringstringWithString:@"a"],@"b",@"c",nil];NSArray*copyArray = [arraycopy];NSMutableArray*mCopyArray = [array mutableCopy];

查看內存,如我們所料,copyArray、mCopyArray和array的內存地址都不一樣,說明copyArray、mCopyArray都對array進行了內容拷貝。同樣,我們可以得出結論:

一圖以蔽之

在集合類對象中,對immutable對象進行copy,是指針復制,mutableCopy是內容復制;對mutable對象進行copy和mutableCopy都是內容復制。但是:集合對象的內容復制僅限于對象本身,對象元素仍然是指針復制。用代碼簡單表示如下:

[immutableObject copy] // 淺復制 + 不可變對象

[immutableObject mutableCopy] //單層深復制 + 可變對象

[mutableObject copy] //單層深復制 + 不可變對象

[mutableObject mutableCopy] //單層深復制 + 可變對象

這個代碼結論和非集合類的非常相似。

集合類的對象中元素的 one-level-deep copy 、two-level-deep copy 和 real-deep copy

集合中的元素,我們按照 下面的順序進行論述:

集合類中對象為Foundation 架構下的

深復制 (one-level-deep copy、two-level-deep copy)

完全復制(real-deep copy)

集合類中對象為自定義對象的

深復制 (one-level-deep copy、two-level-deep copy)

完全復制(real-deep copy)

集合類的對象中的元素為? Foundation 架構下的

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy2 工程,下載完整代碼? OC_Copy

Foundation 架構下的對象 我們以NSString為例,下文中我們所說道淺復制 、深復制、完全復制 都是針對 集合類中的對象而言。

深復制? (單層深copy)

準則

對(集合類) 對象自身執行深copy,對 (集合類)對象內部的元素 執行淺copy,稱為集合類的單層深復制。

代碼

- (void)viewDidLoad {? ? [superviewDidLoad];NSMutableString*mutableString1 = [NSMutableStringstringWithString:@"1"];NSMutableString*mutalbeString2 = [NSMutableStringstringWithString:@"1"];NSMutableArray*mutableArr? ? = [NSMutableArrayarrayWithObjects:mutalbeString2,nil];// mutableString1和mutableArr放入NSArrayNSMutableArray*testArr = [NSMutableArrayarrayWithObjects:mutableString1, mutableArr,nil];NSArray*testArrCopy = [testArrcopy];NSLog(@"%p", testArr);NSLog(@"%p", testArrCopy);NSLog(@"查看數組內部元素的地址---------------");NSLog(@"%p", testArr[0]);NSLog(@"%p", testArrCopy[0]);NSLog(@"%p", testArr[1]);NSLog(@"%p", testArrCopy[1]);}

結果分析

bulid程序,查看內存,發現testArr通過[testArr copy]執行深拷貝,數組自身進行了深拷貝,數組內部的元素執行淺拷貝。2017-03-2016:38:20.416ZYYCopyTest2[6668:282496]數組自身地址0x6000000587502017-03-2016:38:20.416ZYYCopyTest2[6668:282496]數組自身地址0x600000022a802017-03-2016:38:20.417ZYYCopyTest2[6668:282496]查看數組內部元素的地址---------------2017-03-2016:38:20.417ZYYCopyTest2[6668:282496]0x6000000698002017-03-2016:38:20.417ZYYCopyTest2[6668:282496]0x6000000698002017-03-2016:38:20.417ZYYCopyTest2[6668:282496]0x600000058d502017-03-2016:38:20.417ZYYCopyTest2[6668:282496]0x600000058d50

一圖以蔽之

深復制? (雙層深copy)

準則

這里的雙層指的NSArray對象一層 和 NSArray容器內對象 的一層

但不能說完全,因為無法處理NSArray中還有一個NSArray這種情況)

代碼

- (void)viewDidLoad {? ? [superviewDidLoad];NSMutableString*mutableString1 = [NSMutableStringstringWithString:@"1"];NSMutableString*mutalbeString2 = [NSMutableStringstringWithString:@"1"];NSMutableArray*mutableArr? ? = [NSMutableArrayarrayWithObjects:mutalbeString2,nil];// mutableString1和mutableArr放入NSArrayNSMutableArray*testArr = [NSMutableArrayarrayWithObjects:mutableString1, mutableArr,nil];NSArray*testArrCopy = [[NSArrayalloc] initWithArray:testArr copyItems:YES];;NSLog(@"數組自身地址%p %p", testArr,&testArr);NSLog(@"數組自身地址%p", testArrCopy);NSLog(@"查看數組內部元素的地址---------------");NSLog(@"%p", testArr[0]);NSLog(@"%p", testArrCopy[0]);NSLog(@"%p", testArr[1]);NSLog(@"%p", testArrCopy[1]);NSLog(@"查看數組中包含的數組中的元素的地址---------------");// mutableArr中的元素對比,即mutalbeString2對比NSLog(@"%p", testArr[1][0]);NSLog(@"%p", testArrCopy[1][0]);}

結果分析

修改第7行代碼,bulid程序,查看內存,數組和數組中的元素(非數組的)都進行了深拷貝,但是mutalbeString2的指針地址均沒有變化,仍然是淺拷貝。所以這里的 深拷貝不是完全深拷貝 可以理解為 雙層深復制2017-03-2016:48:42.916ZYYCopyTest2[6886:293351]數組自身地址0x60800004f7500x7fff5e9389802017-03-2016:48:42.916ZYYCopyTest2[6886:293351]數組自身地址0x60800003c5e02017-03-2016:48:42.916ZYYCopyTest2[6886:293351]查看數組內部元素的地址---------------2017-03-2016:48:42.916ZYYCopyTest2[6886:293351]0x60800007d4002017-03-2016:48:42.916ZYYCopyTest2[6886:293351]0xa0000000000003112017-03-2016:48:42.916ZYYCopyTest2[6886:293351]0x60800004f6902017-03-2016:48:42.916ZYYCopyTest2[6886:293351]0x60800001d6d02017-03-2016:48:42.917ZYYCopyTest2[6886:293351]查看數組中包含的數組中的元素的地址---------------2017-03-2016:48:42.917ZYYCopyTest2[6886:293351]0x60800007d4402017-03-2016:48:42.917ZYYCopyTest2[6886:293351]0x60800007d440

限制

initWithArray: copyItems:會使NSArray中元素均執行copy方法。這也是我在testArr中放入NSMutableArray和NSMutableString的原因。如果我放入的是NSArray或者NSString,執行copy后,只會發生指針復制;如果我放入的是未實現NSCopying協議的對象,調用這個方法會crash。

一圖以蔽之

完全深復制? (完美copy)

準則

如果想 完美的解決NSArray嵌套NSArray這種情形,使用歸檔、解檔的方式

- (void)viewDidLoad {? ? [superviewDidLoad];NSMutableString*mutableString1 = [NSMutableStringstringWithString:@"1"];NSMutableString*mutalbeString2 = [NSMutableStringstringWithString:@"1"];NSMutableArray*mutableArr? ? = [NSMutableArrayarrayWithObjects:mutalbeString2,nil];// mutableString1和mutableArr放入NSArrayNSMutableArray*testArr = [NSMutableArrayarrayWithObjects:mutableString1, mutableArr,nil];NSArray*testArrCopy = [NSKeyedUnarchiverunarchiveObjectWithData:? ? ? ? ? ? ? ? ? ? ? ? ? ? [NSKeyedArchiverarchivedDataWithRootObject:testArr]];NSLog(@"數組自身地址%p %p", testArr,&testArr);NSLog(@"數組自身地址%p", testArrCopy);NSLog(@"查看數組內部元素的地址---------------");NSLog(@"%p", testArr[0]);NSLog(@"%p", testArrCopy[0]);NSLog(@"%p", testArr[1]);NSLog(@"%p", testArrCopy[1]);NSLog(@"查看數組中包含的數組中的元素的地址---------------");// mutableArr中的元素對比,即mutalbeString2對比NSLog(@"%p", testArr[1][0]);NSLog(@"%p", testArrCopy[1][0]);NSLog(@"修改元素---------------");? ? testArrCopy[1][0] =@"111";NSLog(@"%@", testArr[1][0]);NSLog(@"%@", testArrCopy[1][0]);}

結果分析

修改第7行代碼,bulid程序,查看內存,可見完成了完全深復制,testArr和testArrCopy中的元素,以及容器中容器的指針地址完全不同,所以完成了完全深復制。testArr和testArrCopy數組內的元素值修改互不影響。2017-03-2017:23:45.953ZYYCopyTest2[7303:321658]數組自身地址0x60000005f5c00x7fff5cb5f9802017-03-2017:23:45.954ZYYCopyTest2[7303:321658]數組自身地址0x61000005d9402017-03-2017:23:45.954ZYYCopyTest2[7303:321658]查看數組內部元素的地址---------------2017-03-2017:23:45.954ZYYCopyTest2[7303:321658]0x600000261cc02017-03-2017:23:45.954ZYYCopyTest2[7303:321658]0x61000007fec02017-03-2017:23:45.954ZYYCopyTest2[7303:321658]0x60000005f2f02017-03-2017:23:45.955ZYYCopyTest2[7303:321658]0x61000005d9102017-03-2017:23:45.955ZYYCopyTest2[7303:321658]查看數組中包含的數組中的元素的地址---------------2017-03-2017:23:45.955ZYYCopyTest2[7303:321658]0x600000261d002017-03-2017:23:45.955ZYYCopyTest2[7303:321658]0x61000007ffc02017-03-2017:23:45.955ZYYCopyTest2[7303:321658]修改元素---------------2017-03-2017:23:45.955ZYYCopyTest2[7303:321658]12017-03-2017:23:45.956ZYYCopyTest2[7303:321658]111

限制

這里使用歸檔和解檔的前提是NSArray中所有的對象(NSString)都實現了NSCoding協議。(如果是你自定義的對象,你需要單獨實現NSCoding協議 )

一圖以蔽之

集合類的對象中的元素為 Custom對象

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy3_1 工程,下載完整代碼? OC_Copy

深復制? (單層深copy)

準則

1、對比之前的 NSString,我們需要遵守NSCopying, NSMutableCopying,才可以進行copy和mutableCopy 操作

2、集合類對象自身執行了深copy,集合類對象中的元素執行淺copy

首先我們需要 讓自定義的類實現NSCopying, NSMutableCopying

代碼

@interfacePersion:NSObject@property(nonatomic,copy)NSString*name;@end@implementationPersion- (id)copyWithZone:(nullableNSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;returnp;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;returnp;}@end- (void)viewDidLoad {? ? Persion *p1 = [[Persion alloc] init];? ? p1.name =@"persion1";NSLog(@"p1:%p,%@",p1, p1);? ? Persion *p2 = [p1copy];? ? p2.name =@"persion2";NSLog(@"p2:%p,%@",p2, p2);}

結果分析

由于我們自定義實現的copy和mutableCopy代碼完全是一致的,所以這里換成mutablecopy也一樣,都是執行深拷貝。2017-03-2017:33:26.836ZYYCopyTest2[7462:330502]p1:0x61000001c370,2017-03-2017:33:26.836ZYYCopyTest2[7462:330502]p2:0x61800001c650,

我們將 自定義的 persion 類 放到數組中,來觀察 數組自身和數組內的元素 都執行了哪些copy。

- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);NSMutableArray*arr2 = [arr1 mutableCopy];NSLog(@"arr2: %p, %@", arr2, arr2);}

結果分析

首先應該知道一點 : “數組中只是存儲了對象的地址,而非存儲了對象的本體。”bulid程序,觀察內存,發現arr1和arr2執行了深拷貝,數組內部的元素執行的是淺拷貝。2017-03-2017:42:12.121ZYYCopyTest2[7623:339260]arr1:0x60800004b0d0, ("","","")2017-03-2017:42:12.122ZYYCopyTest2[7623:339260]arr2:0x600000049030, ("","","")

深復制? (雙層深copy)

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy3_2 工程,下載完整代碼? OC_Copy

這里我們介紹2種方法

1、既然數組中的每個元素是淺copy,那么我們對數組中的每個元素再執行一次copy操作。

2、使用 系統提供的 initWithArray:copyItems:? 方法

方法一 代碼

- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);NSMutableArray*arr2 = [NSMutableArraynew];for(inti =0; i < arr1.count; i++) {? ? ? ? Persion *newP = [arr1[i]copy];? ? ? ? [arr2 addObject:newP];? ? }NSLog(@"arr2: %p, %@", arr2, arr2);}

結果分析

arr1和arr2以及各自內部的元素都進行了深拷貝。2017-03-2017:47:51.247ZYYCopyTest2[7718:344280]arr1:0x608000053cb0, ("","","")2017-03-2017:47:51.247ZYYCopyTest2[7718:344280]arr2:0x608000053b90, ("","","")

方法二

- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);NSMutableArray*arr2 = [[NSMutableArrayalloc] initWithArray:arr1 copyItems:YES];;NSLog(@"arr2: %p, %@", arr2, arr2);}

結果分析

同第一種方法一樣 ,arr1和arr2以及各自內部的元素都進行了深拷貝。2017-03-2017:51:55.997ZYYCopyTest2[7824:348246]arr1:0x618000241440, ("","","")2017-03-2017:51:55.998ZYYCopyTest2[7824:348246]arr2:0x61000005faa0, ("","","")

限制

在使用 initWithArray:copyItems:? 方法時候,應該注意傳入的參數? 必須是遵守 NSCopying, NSMutableCopying協議的對象,否者程序crash

如果我們 自定義的類型A中嵌套了其他自定義的類B的對象,那么 A對象(包含A對象內 非B的元素)都是深copy,但B仍是淺copy。

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy3_3工程,下載完整代碼? OC_Copy

代碼如下:

@classSon;@interfacePersion:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,strong) Son *son;@end@interfaceSon:NSObject@property(nonatomic,copy)NSString*name;@end@implementationPersion- (id)copyWithZone:(nullableNSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}@end@implementationSon@end- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? Son *son = [[Son alloc] init];// 這里一定要記得初始化p.son = son;? ? ? ? p.son.name = [NSStringstringWithFormat:@"son%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);//? ? NSMutableArray *arr2 = [NSMutableArray new];//? ? for (int i = 0; i < arr1.count; i++) {//? ? ? ? Persion *newP = [arr1[i] copy];//? ? ? ? [arr2 addObject:newP];//? ? }//? ? NSLog(@"arr2: %p, %@", arr2, arr2);NSMutableArray*arr2 = [[NSMutableArrayalloc] initWithArray:arr1 copyItems:YES];;NSLog(@"arr2: %p, %@", arr2, arr2);? ? Persion *p2 = arr2[0];? ? p2.name =@"new persion";? ? p2.son.name =@"new son";? ? Persion *p1 = arr1[0];NSLog(@"arr1--> p1.name ==%@",p1.name);NSLog(@"arr1--> p1.son.name ==%@",p1.son.name);}

結果分析

上面被注解的創建arr2的代碼和下面2行創建arr2的代碼 運行結果都是一樣的。bulid程序,查看內存,發現和我們料想的一樣,p1.son.name執行了淺copy由于 p1.son.name執行了淺copy 所以 p1.son.name的值 受到了? p2.son.name的 影響。2017-03-2018:11:56.977ZYYCopyTest2[8345:367979] arr1:0x6180000443e0, ("","","")2017-03-2018:11:56.977ZYYCopyTest2[8345:367979] arr2:0x6180000440b0, ("","","")2017-03-2018:11:56.977ZYYCopyTest2[8345:367979] arr1--> p1.name==persion02017-03-2018:11:56.978ZYYCopyTest2[8345:367979] arr1--> p1.son.name==new son

完全深復制? (完美copy)

如想查看以下完整的示例代碼,請從 OC_Copy 文件夾中打開 OC_Copy3_4工程,下載完整代碼? OC_Copy

代碼如下:

@classSon;@interfacePersion:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,copy) Son *son;@end@interfaceSon:NSObject@property(nonatomic,copy)NSString*name;@end@implementationPersion- (id)copyWithZone:(nullableNSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}@end@implementationSon- (id)copyWithZone:(nullableNSZone*)zone {? ? Son *s = [[selfclass] allocWithZone:zone];? ? s.name =self.name;returns;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Son *s = [[selfclass] allocWithZone:zone];? ? s.name =self.name;returns;}@end- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? Son *son = [[Son alloc] init];// 這里一定要記得初始化p.son = son;? ? ? ? p.son.name = [NSStringstringWithFormat:@"son%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);NSMutableArray*arr2 = [[NSMutableArrayalloc] initWithArray:arr1 copyItems:YES];;NSLog(@"arr2: %p, %@", arr2, arr2);? ? Persion *p2 = arr2[0];? ? p2.name =@"new persion";? ? p2.son.name =@"new son";? ? Persion *p1 = arr1[0];NSLog(@"arr1--> p1.name ==%@",p1.name);NSLog(@"arr1--> p1.son.name ==%@",p1.son.name);}

結果分析:

bulid程序,觀察內存,發現這里 數組自身 和 數組內的元素 都是深拷貝, arr1的 元素值 不受 arr2 元素值的影響。這里就是完美復制。2017-03-2017:59:07.546ZYYCopyTest2[7987:354996] arr1:0x608000045490, (? ? "",? ? "",? ? "")2017-03-2017:59:07.546ZYYCopyTest2[7987:354996] arr2:0x610000044770, (? ? "",? ? "",? ? "")2017-03-2017:59:07.546ZYYCopyTest2[7987:354996] arr1--> p1.name ==persion02017-03-2017:59:07.546ZYYCopyTest2[7987:354996] arr1--> p1.son.name ==son0

另外利用歸檔接檔,同樣可以達到上述效果。

準則

對于自定義的類,需要遵循NSCoding協議。

代碼

@classSon;@interfacePersion:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,copy) Son *son;@end@interfaceSon:NSObject@property(nonatomic,copy)NSString*name;@endstaticNSString*NameKey =@"NameKey";staticNSString*SonKey? =@"SonKey";@implementationPersion// 解碼- (nullableinstancetype)initWithCoder:(NSCoder*)aDecoder {self= [superinit];if(self) {? ? ? ? _name = [aDecoder decodeObjectForKey:NameKey];? ? ? ? _son = [aDecoder decodeObjectForKey:SonKey];? ? }returnself;}// 編碼- (void)encodeWithCoder:(NSCoder*)aCoder {? ? [aCoder encodeObject:self.name forKey:NameKey];? ? [aCoder encodeObject:self.son forKey:SonKey];}- (id)copyWithZone:(nullableNSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Persion *p = [[selfclass] allocWithZone:zone];? ? p.name =self.name;? ? p.son? =self.son;returnp;}@end@implementationSon// 解碼- (nullableinstancetype)initWithCoder:(NSCoder*)aDecoder {self= [superinit];if(self) {? ? ? ? _name = [aDecoder decodeObjectForKey:@"name"];? ? }returnself;}// 編碼- (void)encodeWithCoder:(NSCoder*)aCoder {? ? [aCoder encodeObject:self.name forKey:@"name"];}- (id)copyWithZone:(nullableNSZone*)zone {? ? Son *s = [[selfclass] allocWithZone:zone];? ? s.name =self.name;returns;}- (id)mutableCopyWithZone:(NSZone*)zone {? ? Son *s = [[selfclass] allocWithZone:zone];? ? s.name =self.name;returns;}@end- (void)viewDidLoad {NSMutableArray* arr1 = [NSMutableArraynew];for(inti =0; i <3; i++) {? ? ? ? Persion *p = [[Persion alloc] init];? ? ? ? p.name = [NSStringstringWithFormat:@"persion%d",i];? ? ? ? Son *son = [[Son alloc] init];// 這里一定要記得初始化p.son = son;? ? ? ? p.son.name = [NSStringstringWithFormat:@"son%d",i];? ? ? ? [arr1 addObject:p];? ? }NSLog(@"arr1: %p, %@", arr1, arr1);NSMutableArray*arr2 = [NSKeyedUnarchiverunarchiveObjectWithData:? ? ? ? ? ? ? ? ? ? ? ? [NSKeyedArchiverarchivedDataWithRootObject:arr1]];NSLog(@"arr2: %p, %@", arr2, arr2);? ? Persion *p2 = arr2[0];? ? p2.name =@"new persion";? ? p2.son.name =@"new son";? ? Persion *p1 = arr1[0];NSLog(@"arr1--> p1.name ==%@",p1.name);NSLog(@"arr1--> p1.son.name ==%@",p1.son.name);}

結果分析

bulid程序,觀察內存,也同樣實現了完美copy2017-03-2018:23:59.312ZYYCopyTest2[8503:377321] arr1:0x61800004a650, (? ? "",? ? "",? ? "")2017-03-2018:23:59.312ZYYCopyTest2[8503:377321] arr2:0x61800004ae30, (? ? "",? ? "",? ? "")2017-03-2018:23:59.312ZYYCopyTest2[8503:377321] arr1--> p1.name ==persion02017-03-2018:23:59.312ZYYCopyTest2[8503:377321] arr1--> p1.son.name ==son0

注意

如果想做到完美copy,我們需要一層一層的實現? “NSCopying, NSMutableCopying, NSCoding”3個協議的方法 ,這樣代碼量真的好多,后續我們介紹如果利用runtime 來快速進行 歸檔 接檔 自定義類對象。

copy做為 property 修飾關鍵字

UIView及其父類 并沒有像 數組 字典 字符串這些類一樣 遵守 NSCopying, NSMutableCopying 協議,下面代碼程序會crash。

@interface ViewController:UIViewController@property (nonatomic, copy) UIView *view; @end@implementation ViewController- (void)viewDidLoad {? ? [super viewDidLoad];self.copyView = [[UIView alloc] init]; }輸出結果ZYYCopyTest[1153:32849] -[UIView copyWithZone:]: unrecognized selector sent toinstance0x7fa9cf8096f0ZYYCopyTest[1153:32849] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView copyWithZone:]: unrecognized selector sent toinstance0x7fa9cf8096f0'

非集合類對象 使用copy聲明

系統非集合類對象指的是 NSString, NSNumber ... 之類的對象。下面先看個非集合類immutable對象拷貝的例子

@interfaceViewController()@property(nonatomic,copy)NSString*aCopyStr;@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];NSString*str =@"zyy";self.aCopyStr = str;NSLog(@"str輸出:%p,%@\n", str,str);NSLog(@"_aCopyStr輸出:%p,%@\n", _aCopyStr,_aCopyStr);}

bulid程序,觀察內存,發現_aCopyStr 和 str 的內存地址一樣,說明進行了 shallow copy 指針拷貝(淺拷貝)。

再看mutable對象拷貝例子

@interfaceViewController()@property(nonatomic,copy)NSMutableString*aCopyMStr;@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];NSMutableString*mstrOrigin = [[NSMutableStringalloc] initWithString:@"123"];self.aCopyMStr = mstrOrigin;//[self.aCopyMStr appendString:@"6"];//crashNSLog(@"引用計數%@",[mstrOrigin valueForKey:@"retainCount"]);? ? [mstrOrigin appendString:@"321"];NSLog(@"mstrOrigin輸出:%p,%@\n", mstrOrigin,mstrOrigin);NSLog(@"aCopyMStr輸出:%p,%@\n",? _aCopyMStr,_aCopyMStr);}@end

bulid 程序,觀察內存,發現mstrOrigin 和 _aCopyMStr 2個對象的的內存地址不一樣,說明此刻做的是 (deep copy 深復制 (內容拷貝) 。

mstrOrigin值的變化 不影響 _aCopyMStr

將注解的代碼放開,程序會crash。報錯說 self.aCopyMStr是 不可變對象, 這就奇怪了,我聲明時候明明是 NSMutableString *aCopyMStr , 其實這樣因為,在 self.aCopyMStr = mstrOrigin 這一步, aCopyMStr事先用的copy 進行的修飾,程序在執行深拷貝的同時,也返回了一個 不可變對象。

集合類對象 使用copy聲明

針對 NSArray、NSDictionary、NSSet等類進行試驗出現跟NSString類似的現象,不一一列舉,有興趣可以自己去試驗。

copy 和 block 的關系

首先說block的三種類型。

1._NSConcreteGlobalBlock

全局的靜態block,不會訪問外部的變量。就是說block沒有調用其他的外部變量。

2._NSConcreteStackBlock

保存在棧中的 block,當函數返回時會被銷毀。這個block就是沒有被賦值,并且block訪問了外部變量。

3._NSConcreteMallocBlock

保存在堆中的 block,當引用計數為 0 時會被銷毀,對_NSConcreteStackBlock 執行copy得來的。

代碼舉例

- (void)textBlock {// __NSGlobalBlock__^{NSLog(@"1111");}? ? ();// __NSStackBlock__^{NSLog(@"%@",self);}? ? ();// __NSMallocBlock__// 等號是賦值運算, 相當于對block進行copy, 即相當于自動調用了block的copy方法void(^blkStack)(void) = ^{NSLog(@"%d",i);};? ? blkStack(); }

block 使用copy 修飾的原因:

我們知道,函數的聲明周期是隨著函數調用的結束就終止了。我們的block是寫在函數中的。

如果是全局靜態block的話,他直到程序結束的時候,才會被被釋放。但是我們實際操作中基本上不會使用到不訪問外部變量的block。

如果是保存在棧中的block,他會隨著函數調用結束被銷毀。從而導致我們在執行一個包含block的函數之后,就無法再訪問這個block。因為(函數結束,函數棧就銷毀了,存在函數里面的block也就沒有了),我們再使用block時,就會產生空指針異常。

如果是堆中的block,也就是copy修飾的block。他的生命周期就是隨著對象的銷毀而結束的。只要對象不銷毀,我們就可以調用的到在堆中的block。

這就是為什么我們要用copy來修飾block。因為不用copy修飾的訪問外部變量的block,只在他所在的函數被調用的那一瞬間可以使用。之后就消失了。

block 什么時候被自動copy

經過代碼驗證,得出下面的結論,大家有興趣的可以自己再驗證一下。有錯誤的地方,歡迎大伙積極留言指出。

作為變量:

一個 block 剛聲明的時候是在棧上

賦值給一個普通變量之后就會被 copy 到堆上

賦值給一個 weak 變量不會被 copy

作為屬性:

用 strong 和 copy 修飾的屬性會被 copy

用 weak 和 assign 修飾的屬性不會被 copy

函數傳參:

作為參數傳入函數? block不會被 copy

作為函數的返回值? block會被 copy

針對 block 做為參數 傳入函數 block 不會被copy,這些知名的開源庫是這么做的

這里寫圖片描述

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容