绝大多数iOS开发者用过 block
,并且知道用 __weak
的方式去解决循环引用的问题。而进阶一些的开发者则了解 Weak-Strong-Dance
,那么什么是 Weak-Strong-Dance
?它能保证block执行是的 “安全”
吗?
看看下面两段代码的区别,你就明白什么是 Weak-Strong-Dance
了。
- (void)test { __weak typeof(self) weakSelf = self; self.block = ^{ [weakSelf copy]; }; }
- (void)test { __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; [strongSelf copy]; }; }
也就是在用 __weak
解决循环引用的前提下 ,在 block
内部用 __strong
持有对象,试图解决 “在多线程下,可能weakSelf指向的对象会在 Block 执行前被废弃,导致各种各样的问题,比如说KVO,传入nil可是会crash呢”
,如下代码
__weak typeof(self) weakSelf = self; self.handler = ^{ typeof(weakSelf) strongSelf = weakSelf; [strongSelf.obserable removeObserver:strongSelf forKeyPath:kObservableProperty]; };
此时,你可能会这样认为, self 所指向对象的引用计数变成 2,即使主线程中的 self 因为超出作用于而释放,对象的引用计数依然为 1,避免了对象的销毁。
它真的能解决 在多线程下,可能 weakSelf 指向的对象会在 Block 执行前被废弃而导致的问题吗?
答案当然是 否定 的,让我们来看看demo:
不用 Weak-Strong-Dance
:
#import "TestBlock.h" @interface TestBlock () @property (nonatomic, strong) dispatch_block_t block; @end @implementation TestBlock - (void)test { __weak typeof(self) weakSelf = self; self.block = ^{ [weakSelf copy]; }; } @end
看看用clang改写后的代码,这里就只贴关键代码了:
// @interface TestBlock () // @property (nonatomic, strong) dispatch_block_t block; /* @end */ // @implementation TestBlock struct __TestBlock__test_block_impl_0 { struct __block_impl impl; struct __TestBlock__test_block_desc_0* Desc; TestBlock *const __weak weakSelf; __TestBlock__test_block_impl_0(void *fp, struct __TestBlock__test_block_desc_0 *desc, TestBlock *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) { TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy ((id (*)(id, SEL))(void *)objc_msgSend)((id)weakSelf, sel_registerName("copy")); } static void __TestBlock__test_block_copy_0(struct __TestBlock__test_block_impl_0*dst, struct __TestBlock__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __TestBlock__test_block_dispose_0(struct __TestBlock__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __TestBlock__test_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __TestBlock__test_block_impl_0*, struct __TestBlock__test_block_impl_0*); void (*dispose)(struct __TestBlock__test_block_impl_0*); } __TestBlock__test_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__test_block_impl_0), __TestBlock__test_block_copy_0, __TestBlock__test_block_dispose_0}; static void _I_TestBlock_test(TestBlock * self, SEL _cmd) { __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self; ((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__TestBlock__test_block_impl_0((void *)__TestBlock__test_block_func_0, &__TestBlock__test_block_desc_0_DATA, weakSelf, 570425344))); } static void(* _I_TestBlock_block(TestBlock * self, SEL _cmd) )(){ return (*(__strong dispatch_block_t *)((char *)self + OBJC_IVAR_$_TestBlock$_block)); } static void _I_TestBlock_setBlock_(TestBlock * self, SEL _cmd, dispatch_block_t block) { (*(__strong dispatch_block_t *)((char *)self + OBJC_IVAR_$_TestBlock$_block)) = block; } // @end
代码很长,解释下:
在 struct
__TestBlock__test_block_impl_0
里头,我们能看到 TestBlock *const __weak weakSelf;
这代表在 block
内部是以 弱引用
的方式捕获 self
的,这没毛病。重点来了,看这一段代表 block
具体实现的代码块
static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) { TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy ((id (*)(id, SEL))(void *)objc_msgSend)((id)weakSelf, sel_registerName("copy")); }
这里可以看到如果此时外部废弃了 self ,的确会导致 block 内部访问成nil的情况。
那么如果用了 Weak-Strong-Dance
呢?
__weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; [strongSelf copy]; };
看看clang改写后会有什么区别:
struct __TestBlock__test_block_impl_0 { struct __block_impl impl; struct __TestBlock__test_block_desc_0* Desc; TestBlock *const __weak weakSelf; __TestBlock__test_block_impl_0(void *fp, struct __TestBlock__test_block_desc_0 *desc, TestBlock *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) { TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy __attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf; ((id (*)(id, SEL))(void *)objc_msgSend)((id)strongSelf, sel_registerName("copy")); }
区别在于在 block
内多了这么一行代码 __attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;
。
所以持有 self 的行为是在 block 执行的时候才发生的!
回过头来看看问题: 它真的能解决在多线程下,可能 weakSelf 指向的对象会在 Block 执行前被废弃而导致的问题吗?
在执行前就废弃,到了执行的时候, weakSelf
已经是 nil
了,此时执行 __strong typeof(self) strongSelf = weakSelf;
根本没意义吧。
所以在刚才KVO的例子中,该crash还是继续crash吧。只要在执行 __strong typeof(self) strongSelf = weakSelf;
前,对象在其他线程被废弃了,Weak-Strong-Dance不能帮上任何忙!
Weak-Strong-Dance
并不能保证 block所引用对象的释放时机在执行之后
, 更安全的做法应该是在 block
内部使用 strongSelf
时进行 nil检测
,这样可以避免上述情况。