本篇文章的完善版本:對屬性變量賦新值時可能引發的血案
今天review同事的代碼,發現一個bug,如下代碼:
_dataArr = newArr;
其中_dataArr是屬性變量,newArr是局部變量,于是問了如下的幾個問題:
1.掉用屬性使用 __ 與 self. 的區別(不是會調用get方法這么簡單)。
2.修飾數據對象屬性時使用copy與strong的區別。(不是深拷貝與淺拷貝這么簡單)
3.給屬性(比如可變數組)賦值時采用“=”賦值與setArray的區別。
提出后發現對該部分基礎掌握不是很熟悉,所以決定分享下對于初級開發時常常疑惑而出錯的這些地方,于是寫了如下這個測試demo分別演示以上問題所導致的結果,注意觀察局部變量的變化給屬性變量的數據與地址在各種情況下的影響。
#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)
這樣的輸出如果你不覺得意外,那么不用繼續瀏覽下文了。下面仔細談下我的理解。
代碼解析:
-
輸出的a與b:
是通過 _ 獲取的屬性變量,他等同于一個全局變量,所以對于他的修飾copy或strong都不管作用,然后是通過指針賦值引用arr1的地址,從而_mArrStrong和_mArrCopy與arr1指向同一個內存地址,擁有一樣的值。所以_mArrStrong和_mArrCopy會由于arr1的變化而變化。
-
輸出的額外測試1:
arr1指向了一個新的內存地址,數據是@[@"000"],而他之前指向的內存還有_mArrStrong和_mArrCopy引用故不會被釋放,所以arr1改變了,但是不會影響_mArrStrong與_mArrCopy的地址和數據還是保持之前的。
-
輸出的c與d:
是通過self. 獲取的屬性變量,他會在返回 _ 變量前掉用get方法,從而與修飾的copy或strong構成了關聯。若是strong修飾的,則是淺拷貝,地址與值都保持一致,只是多了一個引用;反之若是copy修飾的,則是深拷貝,獲得了一份新的值,但是數據一樣。所以arr2的變化會引起self.mArrStrong的變化,而不會改變self.mArrCopy。
-
輸出的額外測試2:
arr2指向的地址的值改變了,引起指向同一地址的self.mArrStrong的值也改變了,與self.mArrCopy無關。
-
輸出的e與f:
是通過setArray方法賦值,_mArrStrong2與_mArrCopy2的地址不會變化,還是指向初始化時指向的地址,但是擁有了新的值為arr1,之后arr1指向的地址的值發生變化也不會聯系到_mArrStrong2與_mArrCopy2。
-
輸出的g與h:
雖然self.mArrStrong2是淺拷貝,但是通過setArray賦了新值,地址還是初始化的地址,因此不會隨著arr2的變化而變化,self.mArrCopy2同理。
-
特別提醒:
輸出的d,實現的是深拷貝功能,我并沒有給self.mArrCopy申請新的內存,但是系統會自動給它申請。那么是何時申請的呢?就在調用self.mArrCopy時系統會判斷給它new內存,因此我們在初始化時別用 self.mArrCopy = [NSMutableArray array];這樣的方式初始化,因為執行self.mArrCopy時 mArrCopy 為nil,系統會自動new內存, 但是 = 后面又開辟了內存給self.mArrCopy引用,從而導致內存浪費。因此在初始化強引用的屬性對象時用 _ 引用進行初始化,如:_mArrStrong = [NSMutableArray array];
總結如下:
只有采用self.的方式獲取copy修飾的屬性時才會是深拷貝,若是用setArray或是初始化方法賦值時則與修飾無關。無論面向可變的還是不可變的數據對象,包括數組、字符串、字典等都具有一樣的性質。若有不恰當之處請指出。
因此我的慣用寫法是使用copy修飾數據對象,更新數據時用setArray或是初始化方法。這樣做更保險,通過自己管理自己的生命周期,不依賴其它變量。
本篇文章的完善版本:對屬性變量賦新值時可能引發的血案