當我們寫一個block時,如果你的block涉及被self持有以及需要訪問self的成員時示辈,循環(huán)引用問題由此產(chǎn)生太援。解決的辦法也很簡單,其中利用__weak與__strong是常見的手段匪蟀,類似代碼如下:
__weak typeof(self) weakSelf =self;
self.block= ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelfprint];
};
如果要把這個問題說清楚明白,不是件容易的事宰僧,看了很多文章材彪,都沒有深入全面地講明白這個問題。為了深入剖析其中的原理,構建了一個MyTestBlock類段化,類的結構代碼如下:
#import <Foundation/Foundation.h>
@interface MyTestBlock : NSObject
@property (nonatomic, copy) void(^block)();
@end
#import "MyTestBlock.h"
@implementation MyTestBlock
- (void)dealloc
{
NSLog(@"------>dealloc");
}
- (instancetype)init
{
self = [super init];
if (self)
{
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf print];
};
}
return self;
}
- (void)print
{
NSLog(@"---->print");
}
@end
然后在main方法里測試它:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MyTestBlock.h"
int main(int argc, const charchar * argv[])
{
@autoreleasepool
{
MyTestBlock *testBlock = [[MyTestBlock alloc] init];
testBlock.block();
}
while (1) {;} //讓主程序一直運行不退出
return 0;
}
代碼中嘁捷,block屬性為copy,定義為copy屬性是一個存放在堆的block穗泵,堆block從棧復制過程中會復制它所使用的變量的引用(非__block屬性)普气,循環(huán)引用也是因此產(chǎn)生。畫了個簡單的示意圖佃延,如下:
對于代碼中的這一步现诀,是如何打破這個循環(huán)的呢?我們在block外的weakSelf構造了一個指向self的弱引用履肃,如下:
__weak typeof(self) weakSelf = self;
如下圖所示:
如果你的block之行不是異步執(zhí)行仔沿,在顯示調(diào)用testBlock.block()時,就已經(jīng)執(zhí)行尺棋,此時的strongSelf變量是可以省略的封锉,如下:
self.block = ^{
[weakSelf print];
};
打印結果如下:
2017-02-03 12:13:35.881 myBlock[6131:4562526] ---->print
2017-02-03 12:13:35.883 myBlock[6131:4562526] ------>dealloc
雖然循環(huán)引用就打破了,但是新的問題又來了膘螟。那就是會有self提前于block執(zhí)行之前釋放的場景成福,testBlock實體釋放了,self就指向nil了荆残,weakSelf也會被置為nil奴艾,等block回來時,其實在向一個nil發(fā)消息内斯。此時就用到了strongSelf蕴潦,它的作用主要是應對異步執(zhí)行的block,添加異步邏輯如下:
self.block = ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5];
[weakSelf print];
});
};
此時的執(zhí)行結果如下:
2017-02-03 12:19:14.229 myBlock[6181:4566726] ------>dealloc
block中的語句沒有執(zhí)行俘闯,添加strongSelf邏輯之后:
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5];
[strongSelf print];
});
};
5s之后的打印結果如下:
2017-02-03 13:30:23.129 myBlock[6327:4593186] ---->print
2017-02-03 13:30:23.131 myBlock[6327:4593186] ------>dealloc
針對上面的過程潭苞,補充流程圖如下:
有人看到這里,肯定會有疑惑真朗,這里strongSelf的引用不又像前面的self一樣導致了循環(huán)引用了嗎此疹?這里需要好好解釋一下:
self是一個指向實例對象的指針,它的生命周期至少是伴隨著當前的實例對象的遮婶,所以一旦它和對象之間有循環(huán)引用是無法被自動打破的秀菱;strongSelf是block內(nèi)部的一個局部變量,變量的作用域僅限于局部代碼蹭睡,而程序一旦跳出作用域,strongSelf就會被釋放赶么,這個臨時產(chǎn)生的“循環(huán)引用”就會被自動打破肩豁,代碼的執(zhí)行事實上也是這樣子的。
我們可以簡單地驗證一下,首先我們用到一個CoreFoundation中的一個方法CFGetRetainCount清钥,它可以在ARC下獲取retainCount琼锋,修改main函數(shù)中如下:
@autoreleasepool
{
MyTestBlock *testBlock = [[MyTestBlock alloc] init];
NSLog(@"block執(zhí)行前:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
testBlock.block();
NSLog(@"block執(zhí)行后:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
}
執(zhí)行結果如下:
2017-02-03 13:49:34.465 myBlock[6380:4604974] block執(zhí)行前:1
2017-02-03 13:49:34.466 myBlock[6380:4604974] block執(zhí)行后:2
2017-02-03 13:49:39.469 myBlock[6380:4605000] ---->print
2017-02-03 13:49:39.469 myBlock[6380:4605000] ------>dealloc