iOS 把copy聊透

1、前言

copy這個英文單詞,讓我第一個想起的是copy忍者卡卡西。我的很是喜歡卡卡西,和誰對戰都是五五開的上忍。copy翻譯成中文就是複製的意思,爲何咱們想要複製呢?我以爲緣由有下面幾點:面試

  1. 複製更快,重複的東西經過複製,能夠快速獲得一個如出一轍的東西,好比說一個文件,一段文字,一個忍術什麼的。
  2. 更改複製出來的東西,不會影響原來的文件、文字,忍術什麼的,這是咱們的目的

那麼回到iOS開發,其實類比到生活中也差很少。修改複製出來的東西,不但願影響原來的內容。objective-c

針對copy就會引出一些面試題:數組

  1. 定義一個NSString屬性,可使用strong關鍵字修飾嗎?若是能夠,何時使用strong修飾,何時使用copy修飾?
  2. 定義一個NSMutableArray屬性,關鍵字使用copy,像NSMutableArray中添加元素會發生什麼現象?
  3. 涉及到深拷貝,淺拷貝的,NSString NSMutableString NSArray NSMutableArray NSDictionary NSMutableDictionary 調用copy方法或者mutableCopy方法,是深拷貝仍是淺拷貝?

等等...app

下面咱們就來探究一下copyatom

2、實戰

2.1 案例1 NSString

  • 在寫案例以前,咱們應該明確一點,NSString這個類表明不可變字符串。不可變意味着建立出來的字符串對象不能夠被修改
  • 建立一個字符串對象"test",str1指向字符串對象
  • str1調用copy方法,str2指向copy出來的對象
  • str1調用mutableCopy方法,str3指向mutableCopy出來的對象
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *str1 = @"test";
    NSString *str2 = [str1 copy];
    NSMutableString *str3 = [str1 mutableCopy];
    
    NSLog(@"\n str1 -> %@ \n str2 -> %@ \n str3 -> %@", str1, str2, str3);
    NSLog(@"\n str1 -> %p \n str2 -> %p \n str3 -> %p", str1, str2, str3);
    
}
// 打印結果:
2020-06-04 22:47:42.843279+0800 05_copy[34618:3042446] 
 str1 -> test 
 str2 -> test 
 str3 -> test
2020-06-04 22:47:42.843496+0800 05_copy[34618:3042446] 
 str1 -> 0x10c9a0020 
 str2 -> 0x10c9a0020 
 str3 -> 0x600001836fa0
複製代碼

從打印結果能夠看出:spa

  • 不管是調用copy仍是mutaleCopy方法都成功複製了」test「這個文本
  • 從打印內存地址能夠看出str1str2都指向了同一個對象,str3指向了另一個對象。

畫圖分析一波:翻譯

  • 結合上面的內存圖,str1st2都指向了同一個對象,str3指向了另一個對象。
  • 爲何會出現這樣的現象呢?由於調用copy方法會返回一個不可變對象,而調用mutableCopy方法會返回一個可變對象
  • 返回不可變對象,就意味着沒法修改,因此copy執行完畢以後,徹底能夠指向以前的對象,反正沒辦法進行修改,這樣反而節省了內存空間。
  • 返回可變對象,就意味着咱們有修改字符串的需求,只有建立新的對象,修改字符串的時候纔不會影響以前字符串的值。
  • 從而得出一個結論:使用NSString建立的對象,調用copy方法不會建立新的對象,只是指針的拷貝,屬於淺拷貝。而調用mutableCopy方法會建立一個與以前內容同樣的新的對象,屬於深拷貝。

2.2 案例2 NSMutableString

  • 建立一個可變字符串對象,內容是"test",用str1指向該對象
  • str1調用copy方法,使用str2指向返回的對象
  • str1調用mutableCopy方法,使用str3指向返回的對象
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"test"];
    NSString *str2 = [str1 copy];
    NSMutableString *str3 = [str1 mutableCopy];

    NSLog(@"\n str1 -> %@ \n str2 -> %@ \n str3 -> %@", str1, str2, str3);
    NSLog(@"\n str1 -> %p \n str2 -> %p \n str3 -> %p", str1, str2, str3);
}

// 打印結果:
2020-06-05 12:16:42.174359+0800 05_copy[35192:3110215] 
 str1 -> test 
 str2 -> test 
 str3 -> test
2020-06-05 12:16:42.174536+0800 05_copy[35192:3110215] 
 str1 -> 0x600003d282d0 
 str2 -> 0xbf8fdf3650605176 
 str3 -> 0x600003d28270
複製代碼

結果分析:3d

  • 毫無疑問,st1str2str3的內容都是」test「,成功複製
  • 發現,三個指針存儲的內存地址不一樣,說明產生了新的對象

畫圖分析:指針

  • st1是指向的是可變字符串,能夠進行修改
  • str1調用copy方法會從新建立一個新的不可變字符串,是深拷貝,當str1進行修改的時候,str2`中的值不會受到任何的影響
  • str1調用mutableCopy方法會建立一個新的能夠變字符串,那麼就能夠對這個可變字符串進行修改,不影響其str1str2,是深拷貝。而且三者互不影響。

2.3 案例三 NSArray和NSMutableArray

2.3.1 NSArray

  • 操做和上面NSString相似,就再也不說明了,直接看代碼
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray *array1 = @[@"a", @"b", @"c"];
    NSArray *array2 = [array1 copy];
    NSMutableArray *array3 = [array1 mutableCopy];
    
    NSLog(@"\n array1 -> %@ \n array2 -> %@ \n array3 -> %@", array1, array2, array3);
    NSLog(@"\n array1 -> %p \n array2 -> %p \n array3 -> %p", array1, array2, array3);
}
// 打印結果:
2020-06-05 12:32:17.303200+0800 05_copy[35231:3117262] 
 array1 -> (
    a,
    b,
    c
) 
 array2 -> (
    a,
    b,
    c
) 
 array3 -> (
    a,
    b,
    c
)
2020-06-05 12:32:17.303393+0800 05_copy[35231:3117262] 
 array1 -> 0x600003a0c360 
 array2 -> 0x600003a0c360 
 array3 -> 0x600003a0c0f0
複製代碼

打印結果分析:code

  • 從數組的內容角度看,數組中的內容都成功被拷貝
  • array1 array2array3存儲的地址值來看,調用copy方法進行了淺拷貝,而調用mutableCopy方法是深拷貝。也就是說,修改array4裏面的值不會影響array1array2

2.3.2 NSMutableArray

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
    NSArray *array2 = [array1 copy];
    NSMutableArray *array3 = [array1 mutableCopy];
    
    NSLog(@"\n array1 -> %@ \n array2 -> %@ \n array3 -> %@", array1, array2, array3);
    NSLog(@"\n array1 -> %p \n array2 -> %p \n array3 -> %p", array1, array2, array3);
}
// 打印結果:
2020-06-05 12:37:57.830060+0800 05_copy[35251:3120031] 
 array1 -> (
    a,
    b,
    c
) 
 array2 -> (
    a,
    b,
    c
) 
 array3 -> (
    a,
    b,
    c
)
2020-06-05 12:37:57.830295+0800 05_copy[35251:3120031] 
 array1 -> 0x60000378d6e0 
 array2 -> 0x60000378d950 
 array3 -> 0x60000378d9b0
複製代碼

結果分析:

  • 不管調用copy 仍是 mutable 方法都是深拷貝

總結:

在OC中,其實還有不少相似的類好比NSDictionary NSMutableDictionary NSSet NSMutableSet結論都是同樣的。能夠本身去敲一段代碼來驗證。經常使用的我總結以下表:

copy mutableCopy
NSString 返回NSString、淺拷貝 返回NSMutableString、深拷貝
NSMutableString 返回NSString、深拷貝 返回NSMutableString、深拷貝
NSArray 返回NSArray、淺拷貝 返回NSMutableArray、深拷貝
NSMutableArray 返回NSArray、深拷貝 返回NSMutableArray、深拷貝
NSDictionary 返回NSDictionary、淺拷貝 返回NSMutableDictionary、深拷貝
NSMutableDictionary 返回NSDictionary、深拷貝 返回NSMutableDictionary、深拷貝

3、其餘問題

上面已經講清楚了,copymutableCopy針對於不一樣的類返回結果以及是否產生新的對象作了分析和總結。

還遺留了點問題

  1. 在一箇中類定義一個NSString 屬性的時候,NSString一般定義爲copy定義成strong行不行?若是二者都行,開發中該使用哪個?
  2. 定義一個NSMutableArray NSMutableString NSMutableDictionary的屬性,能不能用copy,會不會有什麼問題?

3.1 問題1

  • 以下代碼打印結果是什麼?若是把定義屬性的copy 修改爲 strong,那麼打印結果又是什麼呢?
@interface ViewController ()
@property (nonatomic, copy) NSString *str;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"test"];
    self.str = mStr;
    [mStr appendString:@"haha"];
    NSLog(@"mStr -> %@", mStr);
    NSLog(@"self.str -> %@", self.str);
}
複製代碼
  • copy關鍵字修飾的打印結果:
mstr -> testhaha 
self.str -> test
複製代碼
  • strong關鍵字修飾的打印結果:
mstr -> testhaha 
self.str -> testhaha
複製代碼

這個問題的本質在於這句代碼,這句代碼的本質呢是在調用str的setter方法,最關鍵的地方就是要知道setter方法的內部是怎麼寫的呢?

self.str = mStr;
複製代碼
- (void)setStr:(NSString *)str {
  if (_str != str) {
    [_str release];
    _str = [str copy]; // 最關鍵的地方
  }
}
複製代碼
  • 上面寫的這個setter方法是拋開ARC環境下的寫法,若是傳入的新值和以前保存的值不一致,就先將老的值引用計數-1,新值調用copy方法,賦值給成員變量。
  • 傳入的str是什麼?傳入的str就是mstr,將一個可變字符串進行copy後會建立一個新的對象,_str指向了一個新的對象。因此你再去修改曾經的mStr的值不會影響_str的值。

若是定義字符串屬性的時候,使用strong關鍵字呢?仍是從本質出發,setter方法裏的這句代碼變了。變成retain了。因此_str指向原來的位置,當你修改mStr的值時候,_str確定會跟着改變。

_str = [str retain]; // 最關鍵的地方
複製代碼

若是你還不懂,再畫個圖給你解釋:

3.2 問題2

  • 下面代碼打印結果是什麼?
@interface ViewController ()
@property (nonatomic, copy) NSMutableArray *mArray;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mArray = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
    [self.mArray addObject:@"d"];
  	NSLog(@"%@", self.mArray);
}
複製代碼
  • 沒有打印,直接崩潰
  • 錯誤信息:__NSArrayI找不到 addObject方法,
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600002704060
複製代碼
  • 根據問題1的經驗,你細品,下面這句代碼的本質是什麼?
  • 就是在調用setter方法,因爲是copy修飾,會建立一個新對象,而新對象是不可變數組,不可變數組調用addObject方法怎麼可能找獲得呢?
self.mArray = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
複製代碼

因此最終結果必定是崩潰。

相關文章
相關標籤/搜索