平時開發(fā)中我們遇到block里面引用self的情況,大部分都是這樣處理的
__weak typeof(self) weakSelf = self;
self.myBlock =? ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
};
轉(zhuǎn)載請注明出處:來自LeonLei的博客http://www.gaoshilei.com
我們習(xí)慣了這樣用,貌似這樣用了之后可以解決循環(huán)引用的問題,而且可以保證block執(zhí)行之前self不會被釋放掉?真相總是殘酷的,然而事實并非如此!下面將會對block中引用self的三種方式進(jìn)行討論,并給出原因和另外一種解決方案。
1. block中直接引用self
這種情況使用是block被沒有被self強(qiáng)引用,因此這樣不會導(dǎo)致retain cycle。
dispatch_block_t completionHandler = ^{
NSLog(@"%@", self);
}
2.在block外部創(chuàng)建weakself變量,在block中引用weakself
當(dāng)block被self強(qiáng)引用,此時如果在block內(nèi)強(qiáng)引用self將會導(dǎo)致retain cycle。所以我們就想到了在block外部創(chuàng)建一個weakself,然后block在創(chuàng)建的時候捕獲到的是weakself,這樣就不會導(dǎo)致retain cycle。
__weak typeof(self) weakSelf = self;
dispatch_block_t block =? ^{
[weakSelf doSomething];
[weakSelf doSomethingElse];
};
但是要注意的是block捕獲的是weakself變量,如果在執(zhí)行doSomething的過程中self被釋放掉,由于是弱引用,weakself也將置空,下面的doSomethingElse是無法得到執(zhí)行的,看一個例子:
下面的例子展示的是,在block調(diào)用之后的1秒后釋放self,在block中調(diào)用doSomething,2秒之后再調(diào)用doAnotherThing,意味著調(diào)用doAnotherThing之前self已經(jīng)被釋放了
//viewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.sself = [strongweakself new];
[self.sself test];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.block被釋放!");
self.sself = nil;
});
}
//strongweakself.m
-(void)test
{
self.myobject = [TestObject new];
__weak typeof(self) __weakself = self;
[self.myobject setWeakblock:^{
NSLog(@"調(diào)用block!");
[__weakself doSomething];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[__weakself doAnotherThing];
});
}];
self.myobject.weakblock();
}
-(void)doSomething
{
NSLog(@"%s",__func__);
}
-(void)doAnotherThing
{
NSLog(@"%s",__func__);
}
-(void)dealloc{
NSLog(@"%s",__func__);
}
從打印日志可以看出,block執(zhí)行大約1秒之后self被dealloc,doAnotherThing并沒有得到調(diào)用
2017-01-16 14:31:13.834 strong-weak dance[11366:4727954] 調(diào)用block!
2017-01-16 14:31:13.836 strong-weak dance[11366:4727954] -[strongweakself doSomething]
2017-01-16 14:31:14.893 strong-weak dance[11366:4727954] self.block被釋放!
2017-01-16 14:31:14.893 strong-weak dance[11366:4727954] -[strongweakself dealloc]
所以只使用weakself,在self被釋放之后,weakself由于self的釋放已經(jīng)為空,后面的self都將失效,所以在block中這樣引用self是非常危險的,下面就要談?wù)勎覀冏钍煜さ膕trong-weak dance了。
3.strong-weak dance
對比第二種方案我們看一下doAnotherThing是否可以得到調(diào)用,稍微改一下代碼,還是在block執(zhí)行1秒后釋放self,我們看看后面的self引用是否有效
-(void)test
{
self.myobject = [TestObject new];
__weak typeof(self) __weakself = self;
[self.myobject setWeakblock:^{
NSLog(@"調(diào)用block!");
__strong typeof(self) __strongself= __weakself;
[__strongself doSomething];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[__strongself doAnotherThing];
});
}];
self.myobject.weakblock();
}
此時看打印日志:
2017-01-16 14:36:39.039 strong-weak dance[11374:4728878] 調(diào)用block!
2017-01-16 14:36:39.039 strong-weak dance[11374:4728878] -[strongweakself doSomething]
2017-01-16 14:36:40.110 strong-weak dance[11374:4728878] self.block被釋放!
2017-01-16 14:36:41.213 strong-weak dance[11374:4728878] -[strongweakself doAnotherThing]
2017-01-16 14:36:41.213 strong-weak dance[11374:4728878] -[strongweakself dealloc]
雖然self被釋放掉了,但是并沒有dealloc,因為block內(nèi)部的strongself對他進(jìn)行了一次retain,當(dāng)doAnotherThing執(zhí)行完畢,strongself對他的引用計數(shù)減一,self被dealloc徹底銷毀。
那么問題來了!strong-weak dance能不能解決block執(zhí)行前,self被釋放的問題?下面繼續(xù)驗證
我們改一下代碼,在1秒之后釋放self,在2秒之后執(zhí)行block(注意延時block中對于self的處理是weakself,防止延時block對self進(jìn)行retain影響驗證結(jié)果)
//viewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.sself = [strongweakself new];
[self.sself test];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.block被釋放!");
self.sself = nil;
});
}
//strongweakself.m
-(void)test
{
self.myobject = [TestObject new];
__weak typeof(self) __weakself = self;
[self.myobject setWeakblock:^{
NSLog(@"調(diào)用block!");
__strong typeof(self) __strongself= __weakself;
[__strongself doSomething];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[__strongself doAnotherThing];
});
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",__weakself);
__weakself.myobject.weakblock();
});
}
看一下日志:
2017-01-16 14:44:26.314 strong-weak dance[11395:4730727] self.block被釋放!
2017-01-16 14:44:26.314 strong-weak dance[11395:4730727] -[strongweakself dealloc]
2017-01-16 14:44:27.372 strong-weak dance[11395:4730727] (null)
當(dāng)開始調(diào)用block的時候報錯了,self這時已經(jīng)被dealloc掉。strong-weak dance并沒有解決這種問題??吹竭@心是不是涼了半截?真相就是如此,我們平時一直使用的strong-weak dance也只能解決block得到調(diào)用之后self不被釋放的問題。
這是我們最常用的是一種方案,因為block創(chuàng)建時捕獲的是weakself,所以block執(zhí)行之前不能夠控制self的生命周期,所以這樣不會導(dǎo)致整個block對self進(jìn)行強(qiáng)引用。之后在block內(nèi)部創(chuàng)建一個對self進(jìn)行retain的變量strongself,strongself 作為局部變量強(qiáng)引用了 self 并且會在block執(zhí)行完畢的時候被自動銷毀,這樣既可以保證在block執(zhí)行期間 self 不會被外界干掉,同時也解決了retain cycle的問題。
總結(jié)
通過上面幾個小栗子可以看出來:strong-weak dance確實是比較好的解決方案,但是也不是萬能的,他不能解決block調(diào)用之前self被釋放的問題,下面將block中引用self分為4中場景:
1. 使用self
當(dāng)self不持有、不間接持有block時,可以在block內(nèi)部直接引用self。
2.使用weakself
當(dāng)self持有或間接持有block,可以通過在外部創(chuàng)建self的弱引用weakself然后捕獲到block內(nèi)部進(jìn)行使用,但是這樣使用存在一定風(fēng)險,一般也不推薦使用。
3.使用strong-weak dance
當(dāng)self持有或間接持有block,此時要使用strong-weak dance。
這種方法也不是萬能的,在block被執(zhí)行前,block對self依然只是弱引用,進(jìn)入block里面才會retain一次,保證在block執(zhí)行期間self都不會被釋放掉。
4. block中強(qiáng)引用self并且打破retain cycle
不管是weakself還是strong-weak dance,目的都是避免retain cycle,strong-weak dance的本質(zhì)也是在block中搞了一個局部變量來打破這種循環(huán)引用的;
如果我們在block中直接使用self,并且在適當(dāng)?shù)臅r機(jī)打破這種循環(huán)(比如說在block執(zhí)行完成將這個block銷毀)也可以避免retain cycle,并且這種在block創(chuàng)建時就強(qiáng)引用的方式,在block被調(diào)用前 self 不會被釋放掉,可以彌補(bǔ)strong-weak dance的不足。
關(guān)于本文的內(nèi)容可能存在不足的地方,歡迎大家指正!
參考資料
http://albertodebortoli.com/blog/2013/08/03/objective-c-blocks-caveat