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)載請注明出處,謝謝