我在之前分享了一下Block循环引用的实例应用场景链接地址,当时为了简要(其实是懒
)仅仅只用了__ weak这个关键字进行说明,但是其实相对安全的做法并不是这样,相对安全的做法是什么呢?就是本篇所要讨论的Weak-Strong-Dance.
什么是Weak-Strong-Dance
平时我们在开发中经常使用__ weak来解决block循环引用的问题,也就是这样:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf);
};
这种做法有一个弊端,就是你不知道self什么时候就释放了,上面的例子只是简单的打印了一下问题不大,但是如果你在开发中如果是因为使用这个变量而导致crash,岂不是得不偿失.比如说你把self加入到字典,或者执行self内部的block,此时就会引起crash,那此时Weak-Strong-Dance就起作用了.
为了模拟crash这个场景,我用了以下这个例子,点击一个控制器的界面跳转进另外一个控制器界面,在这个控制器中,给自己的block赋值,然后在这个block中执行当前控制器的另外一个logBlock,在退回上个控制器的时候执行block,代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.logBlock = ^{
NSLog(@"%@",weakSelf);
};
self.block = ^{
weakSelf.logBlock();
};
}
- (IBAction)back:(id)sender {
self.block();
[self dismissViewControllerAnimated:YES completion:nil];
}
此时是没有问题的,没有循环引用,也没有crash,但是我让logBlock延时一段时间执行,也就是这样:
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
weakSelf.logBlock();
});
};
分析:造成这种崩溃的原因是当退出当前控制器的时候,控制器正常释放,而延时执行block里面的代码的时候,控制器已经被释放了,再调用该控制器中的block引起了crash.
此时我稍微改动一下代码,在block中加入如下一段代码:
__strong typeof(self) strongSelf = weakSelf;
也就是这样:
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
strongSelf.logBlock();
});
};
此时程序一切正常,这就是我们的Weak-Strong-Dance,为什么这么神奇呢?空口无凭,让我们通过clang命令来看看最后生成的c++代码:
clang命令:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations SencondViewController.m
SencondViewController.m改成你想编译的文件名,这里只截取我们分析用的一些代码
__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setLogBlock:"), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_0((void *)__SencondViewController__viewDidLoad_block_func_0, &__SencondViewController__viewDidLoad_block_desc_0_DATA, weakSelf, 570425344)));
((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_2((void *)__SencondViewController__viewDidLoad_block_func_2, &__ SencondViewController__viewDidLoad_block_desc_2_DATA, weakSelf, 570425344)));
这部分代码是 __ weak,以及给block赋值的代码
可以看到我们在setBlock的时候构造了一个____ SencondViewController __viewDidLoad_block_impl_2结构体函数地址作为参数进行保存,等下调用block的时候就是根据这个地址去找这个结构体函数里面的 __ SencondViewController __viewDidLoad_block_func_2这个指针
struct __SencondViewController__viewDidLoad_block_impl_2 {
struct __block_impl impl;
struct __SencondViewController__viewDidLoad_block_desc_2* Desc;
SencondViewController *const __weak weakSelf;
//构造结构体
__SencondViewController__viewDidLoad_block_impl_2(void *fp, struct __SencondViewController__viewDidLoad_block_desc_2 *desc, SencondViewController *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//这个函数就是我们执行block调用的函数
static void __SencondViewController__viewDidLoad_block_func_2(struct __SencondViewController__viewDidLoad_block_impl_2 *__cself) {
SencondViewController *const __weak weakSelf = __cself->weakSelf; // bound by copy
__attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_1((void *)__SencondViewController__viewDidLoad_block_func_1, &__SencondViewController__viewDidLoad_block_desc_1_DATA, strongSelf, 570425344)));
}
这部分代码是保存block地址,以便后续调用
static void _I_SencondViewController_back_(SencondViewController * self, SEL _cmd, __strong id sender) {
((dispatch_block_t (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("block"))();
((void (*)(id, SEL, BOOL, void (^ _Nullable __strong)()))(void *)objc_msgSend)((id)self, sel_registerName("dismissViewControllerAnimated:completion:"), ((bool)1), (void (^ _Nullable)())__null);
}
这是调用的代码
更为详细的调用过程可以看一下我之前的一篇文章 (链接地址)
通过观察以上代码可以看出,在给block赋值的时候我们捕获了一下_weakSelf并赋值给结构体的weakSelf变量,调用时候对block进行了一个强引用,此时weakSelf的引用计数器+1,就算此时退出当前页面weakSelf也不会马上释放,会等到延时操作执行完才会释放,所以我们的程序不会崩溃
但是!!!这还不是最保险的,想想这样一个场景,如果此时我们没有马上对weakSelf进行一次强引用操作呢,也就是这么一个比较奇葩的场景:
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
strongSelf.logBlock();
});
});
};
此时程序还是会crash掉,就是因为虽然此时你进行了一次强引用,但是weakSelf在你进行引用之前就已经被释放了,有兴趣的可以试下.
总结
Weak-Strong-Dance这种写法是为了防止我们在调用block时候,引用的对象已经被释放造成一些问题,这种写法可以比较完美的解决我们绝大部分循环引用的问题,但是考虑一些比较奇葩的地方,虽然利用__strong我们进行了强引用,但是还是建议使用之前进行一次非空判断,因为你不能保证你强引用的是不是nil
本篇结束,如发现错误,欢迎指正
网友评论