简述
在我们使用block的时候,我们会使用__weak关键字来避免循环引用.例如
1 2 3 4 5
| __weak MyViewController *wself = self; self.completionHandler = ^ { };
|
但是这种方法会遇到一个问题,就是当执行block中的操作时,self已经被释放了,这就会导致wself为nil,在某些情况下,这会导致应用崩溃.有人会说,如果我们加入一个判断.判断wself不为nil,不就可以了么?事实上,这个方法并不可行.因为有可能在判断执行的时候,wself并不为空,但是当使用wself执行的时候,wself已经变为空值了.
这时候,我们就要使用Strong-Weak Dance
了.
1 2 3 4 5 6
| __weak MyViewController *wself = self; self.completionHandler = ^ { __strong __typeof(wself) sself = wself; };
|
这样可以保证在block运行时,sself不为nil.
有人会问,这个强引用不会导致循环应用吗?这个强引用在这种情况下不会导致循环引用.因为block和self的循环引用是因为block捕获了self.当使用__weak关键字时,block捕获的是wself,这是一个弱引用.在block的运行过程中,一个强引用sself指向self,注意block并没有捕获sself.在block运行的时候,因为有sself的存在,所以self不会被释放,而在block运行结束后,sself的作用域结束,所以sself被释放,指向self的强应用被释放了,就不存在循环应用的问题.总结一下,只要强引用能在 block 中的代码运行完成后被销毁,就不会造成循环引用的问题。而 block 捕获的强引用的生命周期和 block 中的代码无关,和 blcok 本身有关,所以造成了循环引用。
dig deeper
下面使用clang -rewrite-objc生成一段block的c代码.
1 2 3 4 5 6 7 8 9 10 11
| typedef void (^blk)(void); int main(int argc, const char * argv[]) {
NSArray *array = [NSArray array]; typeof(array) __weak warray = array; blk _blk = ^{ typeof(warray) __strong sarray = warray; array; }; return 0; }
|
以上是源代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| typedef void (*blk)(void);
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSArray *__weak warray; NSArray *__strong array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSArray *__weak _warray, NSArray *__strong _array, int flags=0) : warray(_warray), array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSArray *__weak warray = __cself->warray; NSArray *__strong array = __cself->array;
typeof(warray) __attribute__((objc_ownership(strong))) sarray = warray; array; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->warray, (void*)src->warray, 3);_Block_object_assign((void*)&dst->array, (void*)src->array, 3);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->warray, 3);_Block_object_dispose((void*)src->array, 3);}
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) {
NSArray *array = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array")); typeof(array) __attribute__((objc_ownership(weak))) warray = array;
blk _blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, warray, array, 570425344));
return 0; }
|
以上是转换为c后的代码.如果大家阅读过Objective-C高级编程
这本书,相信大家对这段代码感觉不会陌生.这段代码显示了block的本质,它是如何被初始化的等等内容.
我们留意到__main_block_copy_0
和__main_block_dispose_0
这两个函数.这两个函数负责管理block捕获的变量的持有和释放等工作.根据Objective-C高级编程
,在以下四个情况下,会导致
__main_block_copy_0
被调用.
- 调用block的copy方法时.
- block作为函数返回值返回时.
- 将block赋值给附有__strong修饰符id 类型的类或block类型成员变量时.
- 在方法命中含有usingBlock的Cocoa框架方法或者向GCD中的API传递block时.
留意第三条.在程序运行到形如有self.blk = ^{//do something };
这样的语句的时候,__main_block_copy_0就已经被调用了,在这个时候,如果block强引用了self,将会产生循环引用.
留意到,在产生的代码中,__main_block_copy_0
定义如下
1
| static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->warray, (void*)src->warray, 3);_Block_object_assign((void*)&dst->array, (void*)src->array, 3);}
|
在这里,无论是warray或者是array都被作为参数被传递到_Block_object_assign
的调用中,而flag都是3.这就说明_Block_object_assign
对这种情况下的strong和weak是不加区别的(_Block_object_assign会对添加了__block关键字的强应用和弱引用加以区别).这样和我们想象中的不一致.
用clang生成_Block_object_assign的汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| ___copy_helper_block_: ## @__copy_helper_block_ .cfi_startproc ## BB#0: pushq %rbp Ltmp6: .cfi_def_cfa_offset 16 Ltmp7: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp8: .cfi_def_cfa_register %rbp subq $48, %rsp movq %rdi, -8(%rbp) movq %rsi, -16(%rbp) movq -16(%rbp), %rsi movq %rsi, %rdi movq -8(%rbp), %rax movq %rax, %rcx addq $40, %rdi movq %rcx, %rdx addq $40, %rdx movq %rdi, -24(%rbp) ## 8-byte Spill movq %rdx, %rdi movq -24(%rbp), %rdx ## 8-byte Reload movq %rsi, -32(%rbp) ## 8-byte Spill movq %rdx, %rsi movq %rcx, -40(%rbp) ## 8-byte Spill movq %rax, -48(%rbp) ## 8-byte Spill callq _objc_copyWeak movq -40(%rbp), %rax ## 8-byte Reload addq $32, %rax movq -32(%rbp), %rcx ## 8-byte Reload movq 32(%rcx), %rdx movq -48(%rbp), %rsi ## 8-byte Reload movq $0, 32(%rsi) movq %rax, %rdi movq %rdx, %rsi callq _objc_storeStrong addq $48, %rsp popq %rbp retq .cfi_endproc
.align 4, 0x90
|
我们会看到它调用了两个方法,分别是_objc_copyWeak
和_objc_storeStrong
,从这里可以看出weak和strong的确是使用了不用的方法对待的.至于c代码中为什么没有显示出这一点,还有待研究.