看下面一段簡(jiǎn)單而且非常常見的代碼片段,如果和dataArr相關(guān)的代碼不是正如下面的這樣,那么結(jié)果將會(huì)有什么不同嗎?答案是將會(huì)有可能產(chǎn)生非常嚴(yán)重的后果,下面來一一演示下。
@property (nonatomic, copy) NSMutableArray *dataArr;
_dataArr = [NSMutableArray array];
- (void)refreshData:(NSMutableArray *)newArr
{
[self.dataArr setArray: newArr];
......
}
為了模擬實(shí)際開發(fā)中會(huì)遇到的問題,準(zhǔn)備了如下完整的測(cè)試代碼:
@interface ViewController ()
@property (nonatomic, copy) NSMutableArray *dataArr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_dataArr = [NSMutableArray array];
NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@"1", nil];
[self refreshData:mArr];
[mArr addObject:@"3"];
}
- (void)refreshData:(NSMutableArray *)newArr
{
[self.dataArr setArray:newArr];
// self.dataArr = newArr;
// _dataArr = newArr;
NSLog(@"%p %p", _dataArr, newArr);
__block ViewController* bSelf = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[bSelf.dataArr addObject:@"2"];
NSLog(@"%@", bSelf.dataArr);
});
}
@end
測(cè)試:
1.以上代碼輸出結(jié)果是:
1,2
這是正常的代碼方式,dataArr只是使用了newArr的值,不會(huì)再隨著newArr的變化而變化,擁有自己的可變地址空間。
2.將[self.dataArr setArray:newArr]改為self.dataArr = newArr:
運(yùn)行結(jié)果將會(huì)崩在 [bSelf.dataArr addObject:@"2"]; 這一行。
這樣是實(shí)現(xiàn)了深拷貝的功能,但是拷貝出來的類型是不可變的,導(dǎo)致無(wú)法調(diào)用addObject方法。
3.將[self.dataArr setArray:newArr]改為_dataArr = newArr:
輸出結(jié)果是:
1,3,2
_dataArr引用了newArr的地址,導(dǎo)致_dataArr會(huì)隨著newArr值的變化而變化。
4.將修飾dataArr的copy改為strong:
輸出結(jié)果是:
1,2
因此用setArray:更新數(shù)據(jù)時(shí),無(wú)論copy還是strong修飾的屬性都與臨時(shí)變量newArr無(wú)關(guān),擁有自己的內(nèi)存地址。
5.將修飾dataArr的copy改為strong,同時(shí)將[self.dataArr setArray:newArr]改為self.dataArr = newArr:
輸出結(jié)果是:
1,3,2
因?yàn)椴捎玫膕trong為淺拷貝,所以共同引用newArr的地址,導(dǎo)致_dataArr會(huì)隨著newArr值的變化而變化。
6.將修飾dataArr的copy改為strong,同時(shí)將[self.dataArr setArray:newArr]改為_dataArr = newArr:
輸出結(jié)果是:
1,3,2
_dataArr指針指向了newArr指向的地址,這與用什么修飾的dataArr無(wú)關(guān)了。
7.將mArr初始化為不可變數(shù)組,并且將[self.dataArr setArray:newArr]改為self.dataArr = newArr,再注釋掉報(bào)錯(cuò)的[mArr addObject:@"3"]:
通過打印的內(nèi)存地址可以看到self.dataArr 與 newArr地址一樣,并沒有實(shí)現(xiàn)深拷貝,這與上面測(cè)試2中形成了反例。
8.將聲明屬性dataArr的NSMutableArray改為NSArray、NSString、 NSMutableString、NSDictionary、NSMutableDictionary也會(huì)有以上同樣的測(cè)試結(jié)果。
</br>
結(jié)論:
1.由以上的代碼與輸出結(jié)果可以看出,對(duì)于會(huì)接收新值的數(shù)據(jù)對(duì)象請(qǐng)初始化為可變數(shù)據(jù)對(duì)象,并使用[self.dataArr setArray:newArr]或[_dataArr setArray:newArr]這樣的方式賦與新值,如上測(cè)試1輸出時(shí)的代碼方式,不然可能會(huì)導(dǎo)致崩潰或不穩(wěn)定性。
2.如果屬性非要使用不可變數(shù)據(jù)對(duì)象(不推薦),則最好用self.dataArr = [NSArray arrayWithArray:newArr]這樣的方式賦值初始化屬性變量,這樣會(huì)造成頻繁申請(qǐng)內(nèi)存(同時(shí)也會(huì)在釋放)。
3.糾正一下普遍說法,“ copy修飾不可變對(duì)象為淺拷貝,copy修飾可變對(duì)象為深拷貝”,從上面測(cè)試7中可說明這個(gè)說法不成立。
補(bǔ)充:
使用屬性與全局變量的區(qū)別:
1.屬性便于在別的類中調(diào)用與賦值,可添加只讀與只寫修飾;
2.使用屬性時(shí),可以重寫set與get方法實(shí)現(xiàn)一些功能;
2.全局變量可以用static關(guān)鍵字修飾,生命周期和程序相同,只有在此類中可見;
3.全局變量可以用extern關(guān)鍵字修飾給整個(gè)項(xiàng)目共享數(shù)據(jù)(必須保證此變量名在此項(xiàng)目中唯一);
4.全局變量多了一個(gè)可以設(shè)置protected的訪問權(quán)限,但是它們都可實(shí)現(xiàn)private與public的訪問權(quán)限,默認(rèn)聲明在.h中即是public,聲明在.m中即是private的;
5.它們存儲(chǔ)區(qū)不一樣,全局變量存儲(chǔ)在全局區(qū)靜態(tài)區(qū)。
因此,在那些特殊情況下除外盡量使用屬性,并且聲明在.m中,在別的類中需要調(diào)用時(shí),將聲明再剪切到.h中即可。