【iOS】用strong和weak來修飾成員變數的對比

淺淺青丘發表於2018-06-12

對於純程式碼佈局,用@property宣告成員變數時,我是很自然的用strong來修飾的。然後突然有人問我用weak來修飾可不可以,我第一反應是不可以,因為用weak來修飾,初始化過後就會被釋放掉,就算我第一句寫了初始化的方法,立即執行addSubView也是沒辦法將其新增上去的。xcode也給出了很明確的警告:Assigning retained object to weak variable; object will be released after assignment。然後他又給分析了一堆記憶體管理的東西,得出的結論是可以。
由於我之前確實沒有思考過這個問題,對於這個結論我也是半信半疑,然後我就寫了個demo來驗證。自己寫demo之前我想起了之前看過的一些程式碼,有些成員變數是用weak修飾的,初始化方法是將一個臨時變數賦值給它,然後將它加到父View上,我很不理解這樣為什麼這樣寫,直接用strong修飾,然後來個懶載入初始化不是更好?或者直接初始化然後再新增?這樣算起來還少一行程式碼。。。
寫demo的過程中我又發現了一些其他的東西,首先我宣告兩個成員變數,一個strong修飾,一個weak修飾。

@interface ViewController ()

@property(nonatomic,strong) UILabel * strongLabel;
@property(nonatomic,weak) UILabel * weakLabel;

@end

strongLabel使用懶載入來初始化:

-(UILabel *)strongLabel{
    if (!_strongLabel) {
        _strongLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 100, 200, 60)];
        _strongLabel.backgroundColor = [UIColor orangeColor];
        _strongLabel.text = @"我是strong";
        _strongLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _strongLabel;
}

weakLabel直接用上面的懶載入方式來初始化

-(UILabel *)weakLabel{
    if (!_weakLabel) {
        //警告:Assigning retained object to weak variable; object will be released after assignment
        _weakLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 120, 200, 60)];
        _weakLabel.backgroundColor = [UIColor cyanColor];
        _weakLabel.text = @"我是weak";
        _weakLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _weakLabel;
}

測試表明在viewDidLoad方法中呼叫addSubView方法來新增self.weakLabel時,self.weakLabel依然為nil,新增無效。
改進後的weakLabel的懶載入初始化方式:

-(UILabel *)weakLabel{
    if (!_weakLabel) {
        UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(30, 170, 200, 60)];
        label.backgroundColor = [UIColor cyanColor];
        label.text = @"我是weak";
        label.textAlignment = NSTextAlignmentCenter;
        _weakLabel = label;
        [self.view addSubview:self.weakLabel];//如果不加這一句,下面return的時候_weakLabel依然為nil
    }
    return _weakLabel;
}

這樣寫之後在viewDidLoad方法中呼叫addSubView方法來新增self.weakLabel可以新增成功。
還有就是不用懶載入的方式來初始化weakLabel:

-(void)initWeakLabel{
//    _weakLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 170, 200, 60)];
//    _weakLabel.backgroundColor = [UIColor cyanColor];
//    _weakLabel.text = @"我是weak";
//    _weakLabel.textAlignment = NSTextAlignmentCenter;
//    [self.view addSubview:_weakLabel];
    
    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(30, 170, 200, 60)];
    label.backgroundColor = [UIColor cyanColor];
    label.text = @"我是weak";
    label.textAlignment = NSTextAlignmentCenter;
    _weakLabel = label;
    [self.view addSubview:self.weakLabel];//如果不加這一句,在別處呼叫_weakLabel時依然為nil
}

用weak修飾的成員變數初始化之後就會被釋放掉,所以註釋部分的程式碼是無法新增成功的。
以上這些驗證了用weak修飾UI成員變數是可以的,但是就這些而言,我也確實找不到用weak相較用strong而言更好的理由。因為這兩個label新增成功之後唯一的區別就是strongLabel的引用計數為2,weakLabel的引用計數為1,如果後期它們再做同樣的操作,我實在是想不出會有什麼情況strongLabel會出現記憶體洩漏而weakLabel不會出現記憶體洩漏的情況。
後來我試著對這兩個Label進行刪除和再新增的操作,還發現了weak修飾還是要謹慎使用,因為如果weak修飾的UI不是用懶載入來初始化的話,一旦weakLabel被removeFromSuperView了,就會變成nil,再次新增將無法顯示。雖然對這樣的操作更多的人會選擇hide屬性,但是有些場景還是需要用到removeFromSuperView。
最後結論如下:

  • UI成員變數可以用weak來修飾。
  • 用weak修飾的UI成員變數初始化方法與strong修飾的成員變數初始化方法不同(可參考上面的程式碼,如有更好的初始化方法也可分享)。
  • 用weak修飾的成員變數呼叫removeFromSuperView會變成nil,strong修飾的呼叫removeFromSuperView變數地址不變。
  • 再次呼叫被移除的weakLabel無任何效果(除非使用懶載入的方法初始化,此時weakLabel與之前被移除的weakLabel記憶體地址不同),再次呼叫被移除的strongLabel可以新增成功,地址與原地址相同。
  • 也就是對於strongLabel而言,removeFromSuperView只是從View上移除它,但是它依然在記憶體中。而對於weakLabel而言,removeFromSuperView不僅是從View上移除它,也會從記憶體中刪除它。

如果某個UI移除後需要在某種條件下再次顯示初始的情況可以使用weak來修飾。
常用xib的可能比較熟悉weak,因為關聯生成的成員變數都預設是weak修飾的,也可以選擇strong,因為將Label拖到xib上就已經完成了初始化和新增的操作,此時的Label引用計數為1,相當於純程式碼的初始化和新增。以上得出的結論同樣適用於xib上的UI,所以在用xib佈局時,要注意移除在新增的操作。
面試的時候經常被問strong、weak、copy等屬性的區別,這個驗證也很好的證明了weak修飾的變數被釋放後會被置為nil,再次訪問不會發生崩潰的特性。
最後我又驗證了一下用weak來修飾陣列,直接初始化依然無法呼叫,初始化一個變數,然後將該變數賦值為weak陣列後是可以的。
驗證的測試demo地址為:https://github.com/zhanqin/strongAndWeak


相關文章