block 捕获外部变量解析

先看一张全图
这里写图片描述
在上面的图片中能够看到:web

  1. block 内部不能够修改自动变量的值,可是加上__block之后就能够
  2. block 内部能够修改对象属性的值,可是不能够修改对象的指向

接下来会逐个分析app

1. 全局变量

编译前
这里写图片描述
编译后
这里写图片描述
能够发现编译后是直接复制的,没有特殊操做
缘由:全局变量是存放在全局符号表里面的,在整个 app 生命周期均可以访问掉,因此不存在出了做用域会被销毁的问题,所以没有必要作特殊操做svg

2. 全局静态变量

编译前
这里写图片描述
编译后
这里写图片描述
对比发现,block 对于全局静态变量的处理和全局变量同样,直接赋值,缘由也同上。函数

3. 局部静态变量

编译前
这里写图片描述
编译后
这里写图片描述
差异出来了
1. block 语法将其地址做为 block 语法的结构体的一部分,而后经过地址赋值,至关于两个函数之间调用的地址传递,因此能够完美修改
2. 缘由:虽说静态变量存储在静态变量区域,和程序有同样的生命周期,可是他局限在某个函数内部,因此别的函数访问不到,不像全局变量,函数均可以访问到,因此要经过保存他的地址来修改spa

4. 自动变量

编译前
这里写图片描述
编译后
这里写图片描述
1. 如第一张图所见,block 不能对自动变量的值进行修改,可是能够访问
2. 不能修改缘由:在上图标注的地方能够看到 block 截获的自动变量被做为一个结构体成员变量放到了 block 语法的结构体中结构体中,除了值同样,没有任何联系,至关于两个函数之间的值传递,在一个函数中修改并不会影响另一个函数中的值,因此编译器干脆就禁止这样作指针

5. __block 修饰的自动变量

编译前
这里写图片描述
编译后
这里写图片描述
经过对比发现,加了__block 修饰符以后,编译后文件相比于其余的增长了好多,一个个来分析
1. 对于第一处改变,会发如今 block 语法所对应的结构体中,增长了一个__block_byref_block_val_0类型的结构体指针,对于这个结构体,它的结构以下code

struct __Block_byref_block_val_0 {
  void *__isa;//isa 指针,说明他是一个类对象
__Block_byref_block_val_0 *__forwarding;//指向自身
 int __flags;
 int __size;
 int block_val;截获的外部变量的值
};

看了上面的大概知道了,block 语法将带有__block 修饰符的自动变量包装成了一个对象,(oc 是经过 isa 指针来区分不一样的类的,由上面的 isa 能够知道他是一个对象,他有本身的类型,对于类型在后面会提到),而对象又是在堆上的,因此就能够知道,__block 将其修饰的自动变量从栈上拷贝到了堆上,对于堆上的对象,是能够直接修改他的值得,那么踏实经过什么方法复制的呢,看第二条
3. 在上面看到了最后两处被标记的增长的地方,分别为xml

static void __main_block_copy_0()
 static void __main_block_dispose_0()

这两个函数被添加到了static struct __main_block_desc_0 的结构体之中,
由于在 oc 中是不容许对象做为 c 语言的结构体成员的,由于在 ARC 下,oc 对象的生命周期是经过编译器来管理的,可是对于 c 语言结构体来讲,编译器没法分析出其成员的生命周期,因此不能作处理,幸亏 oc 的运行时库可以准确把握 block 从栈上复制到堆上以及堆上的 block 被废弃的时机,因而就经过static void __main_block_copy_0()将其从栈上拷贝到堆上,当 block 被销毁时经过static void __main_block_dispose_0()
函数来销毁
3. 在赋值时看到一个__forwarding指针,指向自身,这是为何呢:由于当 block 从栈上拷贝到堆上以后,咱们须要访问到堆上的对象,这个时候就须要经过栈上的 block 的__forwarding指针来找到堆上的 block,从而保证不论是在栈上仍是堆上,均可以正确访问到 block,栈上的__forwarding指向堆上的 block,堆上的 block 指向自身对象

6. block 捕获外部对象

编译前
这里写图片描述
编译后
这里写图片描述
发现编译后捕获的外部对象被做为 block 语法的结构体的一个成员,由于是 arc 下,因此结构体中的对象的默认修饰符也是 strong,同上面的缘由。arc 下不能将对象做为结构体的成员,由于编译器不能分析其生命周期,因此一样须要上面两个函数来进行 拷贝和释放。blog