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

block循環引用

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

__weak typeof(self) _self = self;

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

__strong typeof(_self) self = _self;

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

__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加載後去下載一張大圖片,然後動態的添加這個圖片到視圖,圖片定位使用約束定位,簡略的場景如下動圖:

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:

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了導致崩潰。

解決

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

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

測試demo在這裏Github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章