Farlanki

Strong-Weak Dance

字数统计: 1.2k阅读时长: 5 min
2016/12/14 Share

简述

在我们使用block的时候,我们会使用__weak关键字来避免循环引用.例如

1
2
3
4
5
__weak MyViewController *wself = self;  
self.completionHandler = ^ {
//do something in block using wself
//do something in block using wself
};

但是这种方法会遇到一个问题,就是当执行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;
//do something in block using sself
//do something in block using sself
};

这样可以保证在block运行时,sself不为nil.
有人会问,这个强引用不会导致循环应用吗?这个强引用在这种情况下不会导致循环引用.因为block和self的循环引用是因为block捕获了self.当使用__weak关键字时,block捕获的是wself,这是一个弱引用.在block的运行过程中,一个强引用sself指向self,注意block并没有捕获sself.在block运行的时候,因为有sself的存在,所以self不会被释放,而在block运行结束后,sself的作用域结束,所以sself被释放,指向self的强应用被释放了,就不存在循环应用的问题.

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; // bound by copy
NSArray *__strong array = __cself->array; // bound by copy

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_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->warray, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

在这里,无论是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代码中为什么没有显示出这一点,还有待研究.

CATALOG
  1. 1. 简述
  2. 2. dig deeper