iOS隨筆——初識Block和Closure的循環(huán)引用

OC中的block和swift中的閉包使得我們能夠優(yōu)雅的解決很多問題,但是其內(nèi)存釋放問題也讓像我這樣的初學(xué)者感到頭疼

1.如何查看程序中的循環(huán)引用

這里簡單的提及兩個我個人比較常用的方法(歡迎大家補(bǔ)充)

  • oc的dealloc中和swift的deinit中打印日志,通過控制器的釋放情況判斷當(dāng)前控制器中是否存在循環(huán)引用
  • Leaks,Xcode自帶的檢測循環(huán)引用的工具,簡潔實(shí)用
2.OC中的Block解析
  • 簡單的循環(huán)引用的例子
UIButton *smartButton = [UIButton buttonWithType:UIButtonTypeCustom];
smartButton.frame = CGRectMake(50, 150, 50, 50);
 [smartButton setBackgroundColor:[UIColor redColor]];
 [self.view addSubview:smartButton];
 [smartButton buttonWithBlock:^(UIButton *button) {
     [self.navigationController popViewControllerAnimated:YES];
 }];
 - (void)dealloc {
    NSLog(@"%s",__func__);
}

當(dāng)我執(zhí)行按鈕方法返回上層頁面時,dealloc沒有被執(zhí)行
我們可以看一下這里的內(nèi)存循環(huán)狀況
vc->smartButton->block->vc
當(dāng)控制器持有block后,block內(nèi)又捕獲了控制,造成循環(huán)引用
那么我們可以從block->vc這里入手,使得block不再強(qiáng)引用控制器,打破這個循環(huán)

__weak typeof(self) weakSelf = self;
    [smartButton buttonWithBlock:^(UIButton *button) {
        [weakSelf.navigationController popViewControllerAnimated:YES];
    }];

這樣dealloc被調(diào)用看到如下打印-[TestViewController dealloc]
關(guān)于block捕獲vc的一點(diǎn)拓展:
在ARC環(huán)境下,對于在堆上_NSConcreteMallocBlock類型的block(即對棧上的block進(jìn)行copy操作后,被復(fù)制到堆上),會有以下特性:
(1) block內(nèi)部如果通過外面聲明的強(qiáng)引用來使用,那么block內(nèi)部會自動產(chǎn)生一個強(qiáng)引用指向所使用的對象。
(2) block內(nèi)部如果通過外面聲明的弱引用來使用,那么block內(nèi)部會自動產(chǎn)生一個弱引用指向所使用的對象。
這也是smartButtton會捕獲vc,淺層的理解


  • 濫用__weak的情況
    很多包括我在內(nèi)的初學(xué)者,存在濫用_weak的問題,遇到Block先weak一下,這是個非常不好習(xí)慣,相信你看到這么一份別人的代碼也會很頭疼
SmartTool *smartTool = [[SmartTool alloc] init];
 [smartTool stupidBlock:^{
     [self.navigationController popViewControllerAnimated:YES];
 }];
[UIView animateWithDuration:1.0 animations:^{
     self.view.backgroundColor = [UIColor orangeColor];
 }];
NSArray *smartArr = @[@"1"];
 [smartArr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
     if ([obj isEqualToString:@"1"]) {
         self.view.backgroundColor = [UIColor orangeColor];
         *stop = YES;
     }
 }];

諸如此類很多代碼就算block中捕獲了控制也不會造成循環(huán)引用,因?yàn)閎lcok并不被vc強(qiáng)引用,更多的思考可以讓你的代碼更加優(yōu)雅


  • strongSelf的使用
__weak typeof(self) weakSelf = self;
 [smartButton buttonWithBlock:^(UIButton *button) {
     __strong typeof(weakSelf) strongSelf = weakSelf;
     [strongSelf doSomething];
     [strongSelf doAnotherThing];
     // ... 可能在一個block會有很多邏輯
     [strongSelf.navigationController popViewControllerAnimated:YES];
 }];

這是一個比較官方的說法,如果你不用strongSelf的話,執(zhí)行方法的過程中可能weakSelf被析構(gòu),從而導(dǎo)致weakSelf = nil,導(dǎo)致方法不被調(diào)用,甚至crash

我們先來看一個問題,可能會困擾初學(xué)者,_strongSelf為什么不會造成循環(huán)引用,block不是也強(qiáng)引用的vc嘛?這和我們第一個造成循環(huán)引用的例子有何不同?

這里只要區(qū)別在于
1.直接強(qiáng)引用,會引用block整個生命周期,造成循環(huán)引用
2.在block內(nèi)強(qiáng)引用,_strongSelf會在Block內(nèi)執(zhí)行完后被釋放,也就是其生命周期在block執(zhí)行時


接著我們來看一下析構(gòu)的例子:

SmartTool *tool = [[SmartTool alloc] init];
 __weak SmartTool *weakTool = tool;
 tool.luckBlock = ^{
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         [weakTool stupidLog];
     });
 };
 tool.luckBlock();

這里在block里面做一個簡單GCD的延時打印方法
執(zhí)行代碼發(fā)現(xiàn)[weakTool stupidLog]方法并沒有被調(diào)用,打印發(fā)現(xiàn)在GCD的block中weakTool已經(jīng)被析構(gòu),為nil
由于tool為局部變量,當(dāng)執(zhí)行完外部代碼,tool被釋放,luckBlock弱引用tool,weakTool當(dāng)執(zhí)行完luckBlock內(nèi)部邏輯后被釋放,當(dāng)延遲2s后再調(diào)用weakTool時,weakTool已經(jīng)為nil

此處需要強(qiáng)引用weakTool,就能正常執(zhí)行打印方法

tool.luckBlock = ^{
   __strong SmartTool *strongTool = weakTool;
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         [strongTool stupidLog];
   });
};

對于此處的兩個block:
1.luckBlock,strongTool對于其是內(nèi)部變量,其生命周期只在luckBlock執(zhí)行時
2.GCDBlock,strongTool對于其是外部變量,GCDBlock會強(qiáng)引用strongTool,直到2s后[strongTool stupidLog]方法被調(diào)用后GCDBlock被銷毀,strongTool也被銷毀

有興趣的同學(xué),可以看一下這段代碼

@property (nonatomic,copy)void(^block)();

__weak typeof(self) weakSelf = self;
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf test];
    });
};
self.block();

  • 主動創(chuàng)建循環(huán)引用的場景
    比如以下需求,當(dāng)你執(zhí)行完一個后臺任務(wù)之后,通知某個實(shí)例對象,做相關(guān)操作,這時候你必須保證相方都存在,并在做完相關(guān)操作之后主動斷開引用
    主動斷開循環(huán)引用的例子:
AFN中,傳入Block是被AFURLSessionManagerTaskDelegate對象引用
而AFURLSessionManagerTaskDelegate被mutableTaskDelegatesKeyedByTaskIdentifier字典引用
AFN在block執(zhí)行完后,mutableTaskDelegatesKeyedByTaskIdentifier字典會移除AFURLSessionManagerTaskDelegate對象
這樣block也被釋放

避免的循環(huán)引用的總結(jié):
1.事先通過weak-strong dance 處理引用關(guān)系
2.事后在合適位置主動斷開


swift中閉包解析

閉包與Block循環(huán)引用的原理是相同的,只是語法上存在一些區(qū)別

  • weak
let smartButton = UIButton(type: .Custom);
smartButton.buttonClickWithClosure { [weak self] (button) in
      self?.doSomething
}
 self.view.addSubview(smartButton);
  • unowned
    weak 和 unowned的語法是一致的,區(qū)別在于
    weak:屬性是可選的,對象銷毀時置nil
    unowned:屬性是不可選的,必須在初始化方法中初始化值,類似oc中的unsafe_unretained
  • strong
let smartButton = UIButton(type: .Custom);
smartButton.buttonClickWithClosure { [weak self] (button) in
      guard let strongSelf = self else { return }
      strongSelf.doSomething
      strongSelf.doAnotherthing
}
self.view.addSubview(smartButton);

如有錯誤,希望大家即時指正
轉(zhuǎn)載請注明出處,謝謝

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

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

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