談?wù)勀銓傩缘恼莆粘潭龋热?_ 與 self. 、copy與strong、“=”賦值與setArray的區(qū)別

本篇文章的完善版本:對屬性變量賦新值時可能引發(fā)的血案

今天review同事的代碼,發(fā)現(xiàn)一個bug,如下代碼:
_dataArr = newArr;
其中_dataArr是屬性變量,newArr是局部變量,于是問了如下的幾個問題:
1.掉用屬性使用 __ 與 self. 的區(qū)別(不是會調(diào)用get方法這么簡單)。
2.修飾數(shù)據(jù)對象屬性時使用copy與strong的區(qū)別。(不是深拷貝與淺拷貝這么簡單)
3.給屬性(比如可變數(shù)組)賦值時采用“=”賦值與setArray的區(qū)別。

提出后發(fā)現(xiàn)對該部分基礎(chǔ)掌握不是很熟悉,所以決定分享下對于初級開發(fā)時常常疑惑而出錯的這些地方,于是寫了如下這個測試demo分別演示以上問題所導(dǎo)致的結(jié)果,注意觀察局部變量的變化給屬性變量的數(shù)據(jù)與地址在各種情況下的影響。


#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSMutableArray *mArrStrong;
@property (nonatomic, copy)   NSMutableArray *mArrCopy;

@property (nonatomic, strong) NSMutableArray *mArrStrong2;
@property (nonatomic, copy)   NSMutableArray *mArrCopy2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //_mArrStrong = [NSMutableArray array];
    //_mArrCopy = [NSMutableArray array];
    _mArrStrong2 = [NSMutableArray array];
    _mArrCopy2 = [NSMutableArray array];
    [self test1];
    [self test2];
}

- (void)test1
{
    NSLog(@"%s", __func__);
    
    NSMutableArray *arr1 = [NSMutableArray arrayWithObjects:@"1", nil];
    _mArrStrong = arr1;
    _mArrCopy = arr1;
    [arr1 addObject:@"2"];
    NSLog(@"%p", arr1);
    NSLog(@"a:%p %@", _mArrStrong, _mArrStrong);
    NSLog(@"b:%p %@", _mArrCopy, _mArrCopy);
    arr1 = (NSMutableArray *)@[@"000"];
    NSLog(@"額外測試:arr1:%p _mArrStrong:%p _mArrCopy:%p %@ \n%@", arr1, _mArrStrong, _mArrCopy, _mArrStrong, _mArrCopy);
    
    NSMutableArray *arr2 = [NSMutableArray arrayWithObjects:@"1", nil];
    self.mArrStrong = arr2;
    self.mArrCopy = arr2;
    [arr2 addObject:@"2"];
    NSLog(@"%p", arr2);
    NSLog(@"c:%p %@", self.mArrStrong, self.mArrStrong);
    NSLog(@"d:%p %@", self.mArrCopy, self.mArrCopy);
    [arr2 setArray:@[@"000"]];
    NSLog(@"額外測試2:arr2:%p self.mArrStrong:%p self.mArrCopy:%p %@ \n%@", arr2, self.mArrStrong, self.mArrCopy, self.mArrStrong, self.mArrCopy);
}

- (void)test2
{
    NSLog(@"%s", __func__);

    NSMutableArray *arr1 = [NSMutableArray arrayWithObjects:@"1", nil];
    [_mArrStrong2 setArray:arr1];
    [_mArrCopy2 setArray:arr1];
    [arr1 addObject:@"2"];
    NSLog(@"%p", arr1);
    NSLog(@"e:%p %@", _mArrStrong2, _mArrStrong2);
    NSLog(@"f:%p %@", _mArrCopy2, _mArrCopy2);
    
    NSMutableArray *arr2 = [NSMutableArray arrayWithObjects:@"1", nil];
    [self.mArrStrong2 setArray:arr2];
    [self.mArrCopy2 setArray:arr2];
    [arr2 addObject:@"2"];
    NSLog(@"%p", arr2);
    NSLog(@"g:%p %@", self.mArrStrong2, self.mArrStrong2);
    NSLog(@"h:%p %@", self.mArrCopy2, self.mArrCopy2);
}

@end

輸出如下:


2016-10-13 19:29:29.018 testArr[40794:1137604] -[ViewController test1]
2016-10-13 19:29:29.018 testArr[40794:1137604] 0x7f89cac0f3b0
2016-10-13 19:29:29.018 testArr[40794:1137604] a:0x7f89cac0f3b0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] b:0x7f89cac0f3b0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] 額外測試:arr1:0x7f89cd000f00 _mArrStrong:0x7f89cac0f3b0 _mArrCopy:0x7f89cac0f3b0 (1, 2) (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] 0x7f89cd00c5d0
2016-10-13 19:29:29.019 testArr[40794:1137604] c:0x7f89cd00c5d0 (1, 2)
2016-10-13 19:29:29.019 testArr[40794:1137604] d:0x7f89cd000f20 (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] 額外測試2:arr2: 0x7f89cd00c5d0 self.mArrStrong: 0x7f89cd00c5d0 self.mArrCopy: 0x7f89cd000f20 (000) (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] -[ViewController test2]
2016-10-13 19:29:29.019 testArr[40794:1137604] 0x7f89cd014250
2016-10-13 19:29:29.019 testArr[40794:1137604] e:0x7f89cae19c10 (1)
2016-10-13 19:29:29.019 testArr[40794:1137604] f:0x7f89cae19b40 (1)
2016-10-13 19:29:29.020 testArr[40794:1137604] 0x7f89cac16e40
2016-10-13 19:29:29.020 testArr[40794:1137604] g:0x7f89cae19c10 (1)
2016-10-13 19:29:29.034 testArr[40794:1137604] h:0x7f89cae19b40 (1)

這樣的輸出如果你不覺得意外,那么不用繼續(xù)瀏覽下文了。下面仔細(xì)談下我的理解。

代碼解析:

  • 輸出的a與b:

是通過 _ 獲取的屬性變量,他等同于一個全局變量,所以對于他的修飾copy或strong都不管作用,然后是通過指針賦值引用arr1的地址,從而_mArrStrong和_mArrCopy與arr1指向同一個內(nèi)存地址,擁有一樣的值。所以_mArrStrong和_mArrCopy會由于arr1的變化而變化。

  • 輸出的額外測試1:

arr1指向了一個新的內(nèi)存地址,數(shù)據(jù)是@[@"000"],而他之前指向的內(nèi)存還有_mArrStrong和_mArrCopy引用故不會被釋放,所以arr1改變了,但是不會影響_mArrStrong與_mArrCopy的地址和數(shù)據(jù)還是保持之前的。

  • 輸出的c與d:

是通過self. 獲取的屬性變量,他會在返回 _ 變量前掉用get方法,從而與修飾的copy或strong構(gòu)成了關(guān)聯(lián)。若是strong修飾的,則是淺拷貝,地址與值都保持一致,只是多了一個引用;反之若是copy修飾的,則是深拷貝,獲得了一份新的值,但是數(shù)據(jù)一樣。所以arr2的變化會引起self.mArrStrong的變化,而不會改變self.mArrCopy。

  • 輸出的額外測試2:

arr2指向的地址的值改變了,引起指向同一地址的self.mArrStrong的值也改變了,與self.mArrCopy無關(guān)。

  • 輸出的e與f:

是通過setArray方法賦值,_mArrStrong2與_mArrCopy2的地址不會變化,還是指向初始化時指向的地址,但是擁有了新的值為arr1,之后arr1指向的地址的值發(fā)生變化也不會聯(lián)系到_mArrStrong2與_mArrCopy2。

  • 輸出的g與h:

雖然self.mArrStrong2是淺拷貝,但是通過setArray賦了新值,地址還是初始化的地址,因此不會隨著arr2的變化而變化,self.mArrCopy2同理。

  • 特別提醒:

輸出的d,實現(xiàn)的是深拷貝功能,我并沒有給self.mArrCopy申請新的內(nèi)存,但是系統(tǒng)會自動給它申請。那么是何時申請的呢?就在調(diào)用self.mArrCopy時系統(tǒng)會判斷給它new內(nèi)存,因此我們在初始化時別用 self.mArrCopy = [NSMutableArray array];這樣的方式初始化,因為執(zhí)行self.mArrCopy時 mArrCopy 為nil,系統(tǒng)會自動new內(nèi)存, 但是 = 后面又開辟了內(nèi)存給self.mArrCopy引用,從而導(dǎo)致內(nèi)存浪費。因此在初始化強(qiáng)引用的屬性對象時用 _ 引用進(jìn)行初始化,如:_mArrStrong = [NSMutableArray array];

總結(jié)如下:

只有采用self.的方式獲取copy修飾的屬性時才會是深拷貝,若是用setArray或是初始化方法賦值時則與修飾無關(guān)。無論面向可變的還是不可變的數(shù)據(jù)對象,包括數(shù)組、字符串、字典等都具有一樣的性質(zhì)。若有不恰當(dāng)之處請指出。

因此我的慣用寫法是使用copy修飾數(shù)據(jù)對象,更新數(shù)據(jù)時用setArray或是初始化方法。這樣做更保險,通過自己管理自己的生命周期,不依賴其它變量。

本篇文章的完善版本:對屬性變量賦新值時可能引發(fā)的血案

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

推薦閱讀更多精彩內(nèi)容