不想廢話,直接上代碼進(jìn)入主題,從基本的循環(huán)引用說起:
在ViewController中聲明一個(gè)block屬性,如下:
@interface ViewController ()
@property (nonatomic, assign) NSInteger times;
@property (nonatomic, copy) void(^block)();
@end
在viewdidLoad中,我們來模擬一下循環(huán)引用:
- (void)viewDidLoad {
[super viewDidLoad];
self.times = 0;
self.block = ^{
self.times += 3;
NSLog(@"%@---%ld", self, self.times);
});
self.block();
}
- (void)dealloc
{
NSLog(@"成功銷毀");
}
上面這段代碼便造成了循環(huán)引用:self對(duì)block屬性有一條強(qiáng)引用,block中又要捕獲_times實(shí)例變量,所以必須必須得保留self,即編譯器自動(dòng)對(duì)self的引用計(jì)數(shù)+1,這就形成了self —> block -> self的"保留環(huán)"。即便pop出當(dāng)前VC,因?yàn)樵摫A舡h(huán)的存在,我們可以看到dealloc方法不會(huì)被調(diào)用,這塊內(nèi)存也不會(huì)被銷毀,這就造成了內(nèi)存泄漏。
這種情況下,一般我們的解決方案是,把該保留環(huán)的一條強(qiáng)指針弱化,一般是:在block前加上:__weak type(self) weakSelf = self
即viewDidLoad里面的代碼變成:
- (void)viewDidLoad {
[super viewDidLoad];
self.times = 0;
__weak type(self) weakSelf = self;
self.block = ^{
weakSelf.times += 3;
NSLog(@"%@---%ld", weakSelf, weakSelf.times);
});
self.block();
}
一般情況下,這么做完全沒問題,但是在下面這種情況下,就出問題了:
__weak typeof(self) weakSelf = self;
self.block = ^{
// 延遲執(zhí)行Block里面的內(nèi)容
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
weakSelf.times += 3;
NSLog(@"%@---%ld", weakSelf, weakSelf.times);
});
};
self.block();
這段代碼表示:我block里面的代碼異步延遲執(zhí)行;
我們?cè)趧俻ush進(jìn)VC的時(shí)候3s內(nèi)又pop出去,發(fā)現(xiàn)雖然dealloc方法被調(diào)用了,程序也沒有崩潰,但是發(fā)現(xiàn)weakSelf變?yōu)榱薾ull。對(duì)_times的+3操作無效,依然為0;
這種情形下問題就出現(xiàn)了,也就是我們Block里面的內(nèi)容還沒執(zhí)行完畢的時(shí)候,當(dāng)前控制器已經(jīng)被pop,因?yàn)閟elf是弱指針,block就不能保留self,self的引用計(jì)數(shù)不在+1。那么self不被強(qiáng)指針引用,當(dāng)然會(huì)變成nil。這就造成了問題,那么怎么辦呢?
這種情況下,就應(yīng)該在block里面讓block生成一個(gè)自動(dòng)變量保持對(duì)這個(gè)弱self的強(qiáng)引用,讓其不會(huì)被銷毀,即:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
strongSelf.times += 3;
NSLog(@"%@", strongSelf);
});
};
self.block();
這樣,即使在3s內(nèi)當(dāng)前控制器被pop,那么block依然會(huì)執(zhí)行,_times的值也順利被+3,問題也就解決了。有人可能就不理解了,你又把self變回強(qiáng)指針,那么又會(huì)回到原來的循環(huán)引用了嗎?其實(shí)不然,這里的strongSelf和self已經(jīng)不是一個(gè)東西了,它對(duì)block是沒有引用的。這個(gè)strongSelf是在block內(nèi)部聲明的局部變量,只有block對(duì)他有引用,當(dāng)block被銷毀時(shí),它也會(huì)跟著被銷毀。所以,這就完美的解決了循環(huán)引用的問題。
當(dāng)然開發(fā)中為了圖省事兒,一般都把該兩句代碼定義為宏,方便直接使用:
#define weakly(objc, weakObjc) __weak typeof(objc) weakObjc = objc;
#define strongly(objc, strongObjc) __strong typeof(objc) strongObjc = objc;
weakly(self, weakSelf);
self.block = ^{
strongly(weakSelf, strongSelf);
[strongSelf doSomething];
});
或者你也可以在libextobjc這個(gè)開源庫中,使用
#import "EXTScope.h"
@weakify(self)
self.block = ^{
@strongify(self)
[self doSomething];
});