OC中的block和swift中的閉包使得我們能夠優(yōu)雅的解決很多問題潭辈,但是其內(nèi)存釋放問題也讓像我這樣的初學者感到頭疼
1.如何查看程序中的循環(huán)引用
這里簡單的提及兩個我個人比較常用的方法(歡迎大家補充)
- oc的dealloc中和swift的deinit中打印日志肾请,通過控制器的釋放情況判斷當前控制器中是否存在循環(huán)引用
- Leaks冰抢,Xcode自帶的檢測循環(huán)引用的工具,簡潔實用
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__);
}
當我執(zhí)行按鈕方法返回上層頁面時党巾,dealloc
沒有被執(zhí)行
我們可以看一下這里的內(nèi)存循環(huán)狀況
vc->smartButton->block->vc
當控制器持有block后吼拥,block內(nèi)又捕獲了控制但校,造成循環(huán)引用
那么我們可以從block->vc
這里入手蕾管,使得block不再強引用控制器,打破這個循環(huán)
__weak typeof(self) weakSelf = self;
[smartButton buttonWithBlock:^(UIButton *button) {
[weakSelf.navigationController popViewControllerAnimated:YES];
}];
這樣dealloc被調(diào)用看到如下打印-[TestViewController dealloc]
關(guān)于block捕獲vc的一點拓展:
在ARC環(huán)境下枷踏,對于在堆上_NSConcreteMallocBlock類型的block(即對棧上的block進行copy操作后,被復(fù)制到堆上)掰曾,會有以下特性:
(1) block內(nèi)部如果通過外面聲明的強引用來使用旭蠕,那么block內(nèi)部會自動產(chǎn)生一個強引用指向所使用的對象。
(2) block內(nèi)部如果通過外面聲明的弱引用來使用,那么block內(nèi)部會自動產(chǎn)生一個弱引用指向所使用的對象掏熬。
這也是smartButtton會捕獲vc佑稠,淺層的理解
- 濫用__weak的情況
很多包括我在內(nèi)的初學者,存在濫用_weak
的問題孽江,遇到Block先weak一下讶坯,這是個非常不好習慣番电,相信你看到這么一份別人的代碼也會很頭疼
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)引用岗屏,因為blcok并不被vc強引用,更多的思考可以讓你的代碼更加優(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
我們先來看一個問題,可能會困擾初學者洞辣,_strongSelf
為什么不會造成循環(huán)引用咐刨,block不是也強引用的vc嘛?這和我們第一個造成循環(huán)引用的例子有何不同扬霜?
這里只要區(qū)別在于
1.直接強引用定鸟,會引用block整個生命周期,造成循環(huán)引用
2.在block內(nèi)強引用著瓶,_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為局部變量沸久,當執(zhí)行完外部代碼,tool被釋放余蟹,luckBlock弱引用tool卷胯,weakTool當執(zhí)行完luckBlock內(nèi)部邏輯后被釋放,當延遲2s后再調(diào)用weakTool時威酒,weakTool已經(jīng)為nil
此處需要強引用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會強引用strongTool佛呻,直到2s后[strongTool stupidLog]方法被調(diào)用后GCDBlock被銷毀裳朋,strongTool也被銷毀
有興趣的同學,可以看一下這段代碼
@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)引用的場景
比如以下需求,當你執(zhí)行完一個后臺任務(wù)之后鲤嫡,通知某個實例對象送挑,做相關(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)載請注明出處丧鸯,謝謝