當我們寫一個block時,如果你的block涉及被self持有以及需要通路self的成員時,循環引用問題由此産生。解決的辦法也很簡單,其中利用__weak與__strong是常見的手段,類似代碼如下:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf print];
};
如果要把這個問題說清楚明白,不是件容易的事,看了很多文章,都沒有深入全面地講明白這個問題。為了深入剖析其中的原理,建構了一個MyTestBlock類,類的結構代碼如下:
//
// MyTestBlock.h
// myBlock
//
// Created by lizitao on 16/10/2.
// Copyright © 2016年 lizitao. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MyTestBlock : NSObject
@property (nonatomic, copy) void(^block)();
@end
//
// MyTestBlock.m
// myBlock
//
// Created by lizitao on 16/10/2.
// Copyright © 2016年 lizitao. All rights reserved.
//
#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方法裡測試它:
//
// main.m
// myBlock
//
// Created by lizitao on 16/10/2.
// Copyright © 2016年 lizitao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MyTestBlock.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
MyTestBlock *testBlock = [[MyTestBlock alloc] init];
testBlock.block();
}
while (1) {;} //讓主程式一直運作不退出
return 0;
}
代碼中,block屬性為copy,定義為copy屬性是一個存放在堆的block,堆block從棧複制過程中會複制它所使用的變量的引用(非__block屬性),循環引用也是是以産生。畫了個簡單的示意圖,如下:
對于代碼中的這一步,是如何打破這個循環的呢?我們在block外的weakSelf構造了一個指向self的弱引用,如下:
__weak typeof(self) weakSelf = self;
如下圖所示:
如果你的block之行不是異步之行,在顯示調用testBlock.block()時,就已經執行,此時的strongSelf變量是可以省略的,如下:
__weak typeof(self) weakSelf = self;
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
雖然循環引用就打破了,但是新的問題又來了。那就是會有self提前于block執行之前釋放的場景,testBlock實體釋放了,self就指向nil了,weakSelf也會被置為nil,等block回來時,其實在向一個nil發消息。此時就用到了strongSelf,它的作用主要是應對異步執行的block,添加異步邏輯如下:
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5];
[weakSelf print];
});
};
此時的執行結果如下:
2017-02-03 12:19:14.229 myBlock[6181:4566726] ------>dealloc
block中的語句沒有執行,添加strongSelf邏輯之後:
__weak typeof(self) weakSelf = self;
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一樣導緻了循環引用了嗎?這裡需要好好解釋一下:
self是一個指向執行個體對象的指針,它的生命周期至少是伴随着目前的執行個體對象的,是以一旦它和對象之間有循環引用是無法被自動打破的;strongSelf是block内部的一個局部變量,變量的作用域僅限于局部代碼,而程式一旦跳出作用域,strongSelf就會被釋放,這個臨時産生的“循環引用”就會被自動打破,代碼的執行事實上也是這樣子的。
我們可以簡單地驗證一下,首先我們用到一個CoreFoundation中的一個方法CFGetRetainCount,它可以在ARC下擷取retainCount,修改main函數中如下:
@autoreleasepool
{
MyTestBlock *testBlock = [[MyTestBlock alloc] init];
NSLog(@"block執行前:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
testBlock.block();
NSLog(@"block執行後:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
}
執行結果如下:
2017-02-03 13:49:34.465 myBlock[6380:4604974] block執行前:1
2017-02-03 13:49:34.466 myBlock[6380:4604974] block執行後:2
2017-02-03 13:49:39.469 myBlock[6380:4605000] ---->print
2017-02-03 13:49:39.469 myBlock[6380:4605000] ------>dealloc
我們發現block執行後,testBlock對象的retainCount加了1,說明block執行時,對testBlock對象臨時加了強引用, 但是最終對象順利dealloc。