block解除了循環引用後還須要注意

block循環引用

block代碼塊在開發中經常使用於異步開發,例如GCD就是提供block的異步塊,同時在使用block的時候每每須要注意避免循環引用,而消除block循環引用就是靠__weak來實現,好比:git

__weak typeof(self) _self = self;

而後在block塊中使用__weak的_self這樣就避免了循環引用的問題。
同時爲了保證在block塊中self不被釋放掉,每每在block塊中添加__strong修飾的引用github

__strong typeof(_self) self = _self;

這樣既避免了循環引用,同時也保障了block塊中使用self的時候不被意外釋放掉。app

__weak typeof(self) _self = self;
[self xxxfunciotnCompletion:^(void){
    __strong typeof(_self) self = _self;
    //使用self不會存在循環引用了
}];

同時還應該注意另一個細節問題,若是在異步的Completion:塊尚未執行前self就被釋放的問題, 由於block塊在執行前是並無強引用self,此時self是可能被釋放的;若是此時再使用self就應該注意nil可能引發的崩潰。
由於__strong typeof(_self) self = _self;只能保障若是此時self沒有被釋放那麼在block塊中使用self期間不會被釋放, 這只是暫時性的強引用self, 隨着block塊的結束而釋放。異步

崩潰示例

present一個控制器BViewController,BViewController加載後去下載一張大圖片,而後動態的添加這個圖片到視圖,圖片定位使用約束定位,簡略的場景以下動圖:ide

clipboard.png

潛在崩潰:在大圖尚未下載完以前就dismiss控制器BViewController使其銷燬,待圖片下載完成回調執行添加約束時崩潰。測試

崩潰分析

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    __weak typeof(self) _self = self;
    [[BigImageDownloader shareDownloader] requestBigImageWithUrl:@"BigImg" completion:^(UIImage * _Nonnull image) {
        __strong typeof(_self) self = _self;
        
        //建立一個UIImageView顯示, 並添加水平居中約束
        UIImageView *bigImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 80, 200, 200)];
        bigImageView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:bigImageView];
        bigImageView.image = image;
        
        //建立約束
        [NSLayoutConstraint constraintWithItem:self.view
                                     attribute:NSLayoutAttributeCenterX
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:bigImageView
                                     attribute:NSLayoutAttributeCenterX
                                    multiplier:1.0f
                                      constant:0.0f].active = YES;
        
        [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
                                     attribute:NSLayoutAttributeTop
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:bigImageView
                                     attribute:NSLayoutAttributeTop
                                    multiplier:1.0f
                                      constant:-80.0f].active = YES;
    }];
}

進入controller後就開始去下載一個圖片,而後把圖片用約束的方式定位顯示到按鈕的上方;
崩潰路徑:進入控制器後,在圖片還沒下載完前就dismiss控制器,使其銷燬,待圖片下載完成後崩潰
崩潰log:ui

2018-11-03 22:13:11.879093+0800 strongSelf[43867:2859446] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSLayoutConstraint for (null): Constraint must contain a first layout item'

根據以上信息,崩潰緣由爲建立約束時的first layout item不能爲null, 而在此例中的first layout item即爲self.view,也就是說self.view爲nil了,由於咱們在圖片下載完成前就dismiss了也就被銷燬了,因此self.view天然爲nil了致使崩潰。spa

解決

在使用block解決循環引用別忘記判斷self是否爲空。code

__weak typeof(self) _self = self;
[self xxxfunciotnCompletion:^(void){
    __strong typeof(_self) self = _self;
    if(!self) return;
}];

測試demo在這裏Github圖片

相關文章
相關標籤/搜索