對(duì)屬性變量賦新值時(shí)可能引發(fā)的BUG

看下面一段簡(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中即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容