__block 说明符的作用以及其对Clang编译器的影响
Posted Haley_Wong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了__block 说明符的作用以及其对Clang编译器的影响相关的知识,希望对你有一定的参考价值。
__block
不同于 __strong
和 __weak
,后面两个是所有权说明符,而__block
是存储域说明符,该说明符会影响变量的存储域。
常见的存储域说明符还有:
- typedef
- extern
- static
- auto
- register
存储域说明符用于指定将变量值设置到哪个存储域中。
auto
表示修饰的变量作为局部变量存储在栈中,这是函数或方法内部默认的声明方式,一般不用添加。auto int count
该语句声明index为一个自动局部变量,意味着进入该模块时,自动为其分配存储空间,退出该模块时自动解除分配。
//下面这两句是等价的
int count;
auto int count;
而static
表示修饰的变量作为静态变量存储在全局变量区。
static
变量有默认的初始值0,而auto
变量没有默认的初始值。除非显式的为auto
变量赋值,否则它的值是不确定的。
extern
修饰的全局变量,也是存储在全局变量区,虽然都是存储在全局变量区,但是extern
与static
还是有很大区别的:extern
表示修饰的全局变量默认是有外部链接的,作用域是整个工程。在一个文件内定义的全局变量,在另一个文件中,通过extern
声明全局变量,就可以使用全局变量;static
修饰的全局静态变量,作用域是声明此变量所在的文件。
我们前几篇中有提到过,在block中使用局部变量时,是不能为变量重新赋值的。
因为block内会创建一个新的变量来存储局部变量的值或者对象的指针,修改的其实不是布局变量,因此编译器就干脆报个错误。
但是如果block中存储局部变量的指针,又可能出现变量被释放,访问野指针的情况而导致Crash。
现在介绍一种新的方法:使用__block
说明符就可以在block内中修改外部变量的值(即可以为变量赋值)。
那为何添加__block
,就可以重新赋值?
添加__block
,编译器对源码的转换会发生什么变化呢?
1.源码实战
首先,依然是编写一段测试代码,使用clang -rewrite-objc
命令,将源码转换为C语言代码。
转换前的原始代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
__block int count = 3;
__block int length = 10;
void (^blk)(void) = ^
count = 5;
length = 20;
NSLog(@"__block 测试 %d", count);
;
count = 10;
length = 100;
blk();
return 0;
经Clang 转换后的Block相关C语言代码如下:
// 被__block修饰的变量会被转换为如下结构体
struct __Block_byref_count_0
void *__isa;
__Block_byref_count_0 *__forwarding;
int __flags;
int __size;
int count;
;
struct __Block_byref_length_1
void *__isa;
__Block_byref_length_1 *__forwarding;
int __flags;
int __size;
int length;
;
struct __main_block_impl_0
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_count_0 *count; // by ref
__Block_byref_length_1 *length; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count, __Block_byref_length_1 *_length, int flags=0) : count(_count->__forwarding), length(_length->__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_count_0 *count = __cself->count; // bound by ref
__Block_byref_length_1 *length = __cself->length; // bound by ref
(count->__forwarding->count) = 5;
(length->__forwarding->length) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rp_rp_9nrd50kq_0n87d_r7fn1c0000gn_T_main_1c6300_mi_0, (count->__forwarding->count));
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) _Block_object_assign((void*)&dst->count, (void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->length, (void*)src->length, 8/*BLOCK_FIELD_IS_BYREF*/);
static void __main_block_dispose_0(struct __main_block_impl_0*src) _Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->length, 8/*BLOCK_FIELD_IS_BYREF*/);
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[])
__attribute__((__blocks__(byref))) __Block_byref_count_0 count = (void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 3;
__attribute__((__blocks__(byref))) __Block_byref_length_1 length = (void*)0,(__Block_byref_length_1 *)&length, 0, sizeof(__Block_byref_length_1), 10;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_count_0 *)&count, (__Block_byref_length_1 *)&length, 570425344));
(count.__forwarding->count) = 10;
(length.__forwarding->length) = 100;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
2.分析
首先,Clang会将__block
修饰的变量,转换出一个结构体类型。
结构体类型命名是__Block_byref_
+ 变量名 + _
+ index(变量出现的顺序)。
所以这里使用的两个变量类型分别是:__Block_byref_count_0
和__Block_byref_length_1
。
而该__block结构体类型的结构是:
- 第一个参数
__isa
指针, - 第二个参数
__forwarding
指针,指向自己的内存地址。 - 第三个参数
__flags
,是标志性参数,暂时没用到所以默认为0。 - 第四个参数
__size
,是该结构体所占用的大小。 - 第五个参数就是与外部类型和名称一致的实际变量。
__main_block_impl_0
基本上跟上一节一样,依然是有impl
和Desc
两个实例变量,以及block内部使用到的两个新的__block
变量类型的变量。
注意
__main_block_impl_0
中存储的并不是__Block_byref_count_0
和__Block_byref_length_1
两个变量,而是这两个变量的指针地址,这样做是为了能够在多个block中访问到同一个__block
变量。
需要特别注意的是__main_block_func_0
函数的实现。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
__Block_byref_count_0 *count = __cself->count; // bound by ref
__Block_byref_length_1 *length = __cself->length; // bound by ref
(count->__forwarding->count) = 5;
(length->__forwarding->length) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rp_rp_9nrd50kq_0n87d_r7fn1c0000gn_T_main_1c6300_mi_0, (count->__forwarding->count));
这里首先依然是通过函数传入的形参__cself
,取到内部的实例变量count 和length。
count 和length分别是__Block_byref_count_0
类型和__Block_byref_length_1
。
其实这里,可以直接使用count->count = 5以及length->length = 20就完事了。
- 那为什么要添加
__forwarding
这个实例变量? - 为什么要
(count->__forwarding->count) = 5
这样赋值呢?
添加__forwarding
是为了存储变量的内存地址,在对block进行copy之前,__forwarding
中存的是自己的内存地址,所以访问count->__forwarding
就等于访问自身;但是对block进行copy,从栈区拷贝到堆区后,__Block_byref_count_0
这样的变量也会从栈区拷贝到堆区,所以就会有一个生成一个新的变量,这时候原来的变量里的count->__forwarding
存储的就是堆区中的变量的内存地址。
所以,(count->__forwarding->count) = 5
这样赋值,可以保证不管变量是否从栈区拷贝到了堆区,都能正确的访问实际的变量。
__main_block_copy_0
和 __main_block_dispose_0
这俩函数,就是为了系统在合适的时机,将block从栈区拷贝到堆区时,对变量进行赋值和销毁。
最后来看看最关键的main()
,去掉一些多余的转换如下:
int main(int argc, const char * argv[])
__Block_byref_count_0 count = (void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 3;
__Block_byref_length_1 length = (void*)0,(__Block_byref_length_1 *)&length, 0, sizeof(__Block_byref_length_1), 10;
blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &count, &length, 570425344));
(count.__forwarding->count) = 10;
(length.__forwarding->length) = 100;
(*blk->impl.FuncPtr)(blk);
return 0;
这里首先创建了两个新结构体类型的变量__Block_byref_count_0 count
和__Block_byref_length_1 length
。
然后,再构造一个__main_block_impl_0实例。
另外,还有一个需要注意的是,__main_block_impl_0
构造函数的最后一个参数Flags
不再是默认的0了。
如果在block外,修改__block
变量,则也使用(count.__forwarding->count)
来赋值。可以保证不管是block内,还是block外访问的都是同一个变量。
注意 和 总结
这里只有block中使用到了__block
变量,Flags
才不是默认值0。
count.__forwarding->count
这样访问,可以保证所访问变量的一致性(不管是否经过栈到堆的拷贝)。
以上是关于__block 说明符的作用以及其对Clang编译器的影响的主要内容,如果未能解决你的问题,请参考以下文章