__block在什么情况下使用? 前一篇文章中我们知道了block拥有捕获变量的能力,但是block却不能改变自动变量的值,每当我们尝试着在block中修改某个自动变量的时候编译器都会抛出一个错误Variable is not assignable (missing __block type specifier)
,表示该变量在block中不可被赋值,需要使用__block修饰。但是当我们尝试在block中去修改全局变量、静态变量、全局静态变量却是可以通过的,这是为什么?
block捕获到的自动变量只是该变量的值,而全局变量、静态变量、全局静态变量的值为什么能修改? 全局变量和静态全局变量是存储在内存全局区,因为作用域的原因使得他们可以在block中被修改。 静态变量被block捕获的是内存地址的值,所以也能被修改。
__block修饰符都做了些啥? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main (int argc, const char * argv[]) { @autoreleasepool { __block int val = 1 ; __block NSString *str = [NSString string ]; NSLog(@"val初始化地址-%p" , &val); NSLog(@"str初始化地址-%p" , &str); void (^aBlock)(void ) = ^{ val++; str = @"a" ; NSLog(@"val block地址-%p" , &val); NSLog(@"str block地址-%p" , &str); }; NSLog(@"%@" , aBlock); aBlock(); } return 0 ; }
老套路,clang -rewrite-objc maim.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; int val; }; struct __Block_byref_str_1 { void *__isa; __Block_byref_str_1 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void *, void *); void (*__Block_byref_id_object_dispose)(void *); NSString *str; };
头部先是建了两个结构体,分别是基础数据类型和对象数据类型,基础数据类型结构体有五个成员: 1、isa指针 2、指向自身类型的指针 3、标记flag 4、占用内存大小 5、变量的值 而对象数据类型则多出了__Block_byref_id_object_copy()
和__Block_byref_id_object_dispose()
,从字面上看着两个函数大概是用于内存管理的。
附上重写后的完整代码,一步一步来分析
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 45 46 struct __main_block_impl_0 { struct __block_impl impl ; struct __main_block_desc_0 * Desc ; __Block_byref_str_1 *str; __Block_byref_val_0 *val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_str_1 *_str, __Block_byref_val_0 *_val, int flags=0 ) : str(_str->__forwarding), val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; __Block_byref_str_1 *str = __cself->str; (val->__forwarding->val)++; (str->__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_tr_t3h53q856n11q1hrk0gc6bgm0000gn_T_main_00d5a1_mi_2; static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void *)&dst->str, (void *)src->str, 8 ); _Block_object_assign((void *)&dst->val, (void *)src->val, 8 ); } static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void *)src->str, 8 ); _Block_object_dispose((void *)src->val, 8 ); } 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[]) { { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void *)0 ,(__Block_byref_val_0 *)&val, 0 , sizeof (__Block_byref_val_0), 1 }; __attribute__((__blocks__(byref))) __Block_byref_str_1 str = {(void *)0 ,(__Block_byref_str_1 *)&str, 33554432 , sizeof (__Block_byref_str_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString" ), sel_registerName("string" ))}; void (*aBlock)(void ) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_str_1 *)&str, (__Block_byref_val_0 *)&val, 570425344 )); ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock); } return 0 ; }
可以看见block的构造函数中isa是个_NSConcreteStackBlock,但是在__main_block_desc_0()中出现了( copy) 和( dispose),这两个函数会将block推入堆中以及释放block(详情可查阅从源码分析Block的内存管理 )。
从main函数开始,先是初始化了val变量,注意观察第二个成员也就是__forwarding
,他好像指向了val自己?带着这个疑问咱们继续往下走,NSLog直接忽略,在block构造的时候有个参数是fp
也就是__main_block_func_0
,这个参数就是block内部回调方法的实现。
1 2 3 4 5 6 7 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; __Block_byref_str_1 *str = __cself->str; (val->__forwarding->val)++; (str->__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_tr_t3h53q856n11q1hrk0gc6bgm0000gn_T_main_00d5a1_mi_2; }
原本我们代码中写的val++
,clang重写之后变成了(val->__forwarding->val)++
,然后再带上之前__forwarding
指针指向的疑问,有点凌乱。。。一步一步捋过来吧,先run一下代码
1 2 3 4 5 val初始化地址-0x7ffeefbff5e8 str初始化地址-0x7ffeefbff5c8 <__NSMallocBlock__: 0x1007235f0> val block地址-0x100723568 str block地址-0x100523608
执行完之后可以发现block已经变成了NSMallocBlock
,而val
和str
的地址与block的地址相差很小,很大概率上他们也存在了堆区,然后结合下图去分析
__cself->val
指向的是栈区的val
,但是block中val
已经被复制到了堆区,堆区的val
指向自己没有任何的问题,但是当访问的是栈区的val
要怎样才能正确的访问到变量的值?__forwarding
被设计出来就是为了解决这个问题,如上图所示,栈区的 __forwarding
指向了复制到堆后的 val
,而堆上val
指向自己,这也就保证了不论是访问栈还是堆上的val
都能获取到正确的值。
再看看重写后的(val->__forwarding->val)
,是不是已经明白了?