copy關鍵字

相信對於有一定iOS開發經驗的同學來說,對於copy關鍵字一定不陌生,從字義上來看,應該就是複製一個對象,然後我們對於NSString類型的屬性,一般也用copy關鍵字。但是大家對於copy關鍵字真正有什麼具體瞭解呢,什麼時候用copy,什麼時候用mutableCopy,區別又在哪裏,對於內存存儲上又有什麼知識點,我相信還有一部分同學一知半解。秉着鑽研探索的精神,我們來詳細的學習一下。

首先我們先說兩個兩個概念:

淺複製:不拷貝對象本身,僅僅是拷貝指向對象的指針
深複製:是直接拷貝整個對象內存到另一塊內存中


淺複製與深複製(圖片來自網絡)

一般來說像這種使用‘=’號賦值的對象,基本上都是淺複製

   UIView * view1 = [[UIView alloc]init];
   UIView * view2 = [[UIView alloc]init];
   view1 = view2;

屏幕快照 2016-11-22 下午4.55.28.png

內存地址一樣的,很簡單,所以它也是我們說的淺複製之一。

然後我們來來看copy這關鍵字;

copy的字面意思就是“複製”,它是產生一個副本的過程,再來看在iOS裏,copy與mutableCopy都是NSObject裏的方法,一個NSObject的對象要想使用這兩個函數,那麼類必須實現NSCopying協議或NSMutableCopying協議,並且是實現了一般來說我們用的很多系統裏的容器類已經實現了這些方法。


屏幕快照 2016-11-22 下午5.12.47.png

如果不遵守協議,直接使用[xxx copy],那麼會直接導致程序崩潰,比如UIView這個類就不允許使用copy

'NSInvalidArgumentException', reason: '-[UIView copyWithZone:]: unrecognized selector sent to instance 0x7fd5605099f0'
*** First throw call stack:
some error....

然後我們再來看copy關鍵字的特點:
修改源對象的屬性和行爲,不會影響副本對象
修改副本對象的屬性和行爲,不會影響源對象
一個對象可以通過copy和mutableCopy方法來創建一個副本對象
copy:創建的是不可變副本(NSString,NSArray,NSDictionary)
mutableCopy:創建的是可變副本(NSMutableString,NSMutableArray,NSMutableDictionary)

原則就是:修改新(舊)對象,不影響舊(新)對象!而且不一定產生新的對象!(劃重點)

看個例子:

   NSString * str = @"testStr";
   NSMutableString * mutableStr = [str mutableCopy];
   NSLog(@"%@,%p",str,str);
   NSLog(@"%@,%p",mutableStr,mutableStr);

打印

testStr,0x103b9f068
testStr,0x600000264d80

可以看到兩個對象的內容完全一樣,但是地址空間變了,說明開闢了一塊新內存供給副本,爲什麼這個會產生新的對象呢?
1.因爲原則 修改新(舊)對象,不影響舊(新)對象,所以生成一個新的對象
2.因爲以前的對象是個不可變對象,而通過mutableCopy拷貝出來的對象必須是一個可變的對象,所以必須生成一個新的對象

同理:

NSMutableString * mutableStr = [NSMutableString stringWithFormat:@"mutableStr"];
NSMutableString * str = [mutableStr mutableCopy];
[str appendString:@"123"];
NSLog(@"%@,%p",mutableStr,mutableStr);
NSLog(@"%@,%p",str,str);

打印

mutableStr,0x6080000778c0
mutableStr123,0x608000077bc0

文字內容不同,對象地址不同,修改新(舊)對象,不影響舊(新)對象

相同的

  NSMutableString * mutableStr = [NSMutableString stringWithFormat:@"mutableStr"];
  NSString * str = [mutableStr copy];
  NSLog(@"%@,%p",mutableStr,mutableStr);
  NSLog(@"%@,%p",str,str);

打印

mutableStr,0x600000075900
mutableStr,0x600000035360

原理一樣,使用copy關鍵字,產生了一個新的不可變的對象

以上的例子我們可以發先,使用copy或者mutableCopy都有產生新對象,現在我們再來看一個例子

 NSString * str = @"str";
 NSString * copyStr = [str copy];
 NSLog(@"%@,%p",str,str);
 NSLog(@"%@,%p",copyStr,copyStr);

打印

str,0x10c65e068
str,0x10c65e068

這下我們發現,兩個對象的內存地址完全一樣,所以系統並沒有創建一個新對象,這是爲什麼呢?
當我們對一個不可變對象(NSString類型)使用copy關鍵字的時候,系統是不會產生一個新對象,因爲原來的對象是不能修改的,拷貝出來的對象也是不能修改的,那麼既然兩個都不可以修改,所以這兩個對象永遠也不會影響到另一個對象(符合我們說的“修改新(舊)對象,不影響舊(新)對象”原則),系統爲了節省內存,所以就不會產生一個新的對象了。

那麼問題來了, copy到底是深拷貝還是淺拷貝?
我相信有的同學認爲只要是使用copy關鍵字,那麼肯定都是深拷貝,這樣是很不嚴謹的,就比如上個例子,雖然使用了copy,但是指針地址是一樣,那麼它就應該是淺拷貝。
所以是否是深淺拷貝,是否創建新的對象,是由程序運行的環境所造成的,並不是一概而論。

對於NSArray,NSDictionary,道理也是相同的。

現在再讓我們看下copy的內存管理:

淺拷貝不會生成新的對象,所以系統會對以前的對象進行一次retain,深拷貝會產生新的對象,系統不會對以前的對象進行retain。

接着我們來看下copy與Block的配合使用

首先我們還是回顧一個概念

block默認存儲在棧中,棧中的Block訪問到的外界對象,不會對應進行retain
block如果在堆中,在block中訪問了外界的對象,會對外界的對象進行一次retian

因爲block在什麼時候執行是不確定的,所以如果block裏外部對象被提前釋放了,那麼如果這時候block執行了,造成野指針異常,程序crash。

所以對於Block來說,我們一般都用copy關鍵字修飾.

#import <Foundation/Foundation.h>

typedef void(^TestBlock)(NSString * str);

@interface Model : NSObject

@property (nonatomic,copy) TestBlock testblock;

@end

使用copy保存block,這樣可以保住block中,避免以後調用block的時候,外界的對象已經釋放了



作者:司機王
鏈接:http://www.jianshu.com/p/700f58eb0b86
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
發佈了72 篇原創文章 · 獲贊 70 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章