1. 程式人生 > >iOS中關鍵字copy與mutableCopy的詳解,看我你就都懂了

iOS中關鍵字copy與mutableCopy的詳解,看我你就都懂了

對於有一定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的時候,外界的物件已經釋放了