一、最简单的block
1、最简单的block结构
^{
NSLog(@"this is a block");
NSLog(@"this is a block");
NSLog(@"this is a block");
};
2、block的调用
^{
NSLog(@"this is a block");
NSLog(@"this is a block");
NSLog(@"this is a block");
}();
void (^block)(void) = ^{
NSLog(@"this is a block");
NSLog(@"this is a block");
NSLog(@"this is a block");
};
block();

二、block的底层结构
- 使用终端
cd到main.m文件所在文件夹, 并执行下述命令行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- 将生成的
main.cpp文件拖到项目中并打开, 可以看到编译后的main函数, 可以看到block的定义和调用

- 在
main.cpp中还可以找到如下代码

- 可以看到
block在底层的结构是__main_block_impl_0结构体 block中的代码块也封装成了一个函数__main_block_func_0__main_block_impl_0的第一个参数__block_impl结构体如下

- 所以
__main_block_impl_0结构体可以看成下图的样子, 因为第一个成员变量是isa, 所以block本质就是一个OC对象

- 再看
main函数, 将创建block对象时的强制转换类型删掉, 可以看到下面的样子

-
创建
__main_block_impl_0时, 传入两个参数, 第一个就是封装了block代码块的__main_block_func_0函数的地址, 第二个是block的描述结构体__main_block_desc_0(0,__main_block_impl_0占用内存大小) -
接着查看
__main_block_impl_0的构造函数

- 可以看到
__main_block_func_0的函数地址赋值给了__block_impl结构体的成员变量FuncPtr,block的描述赋值给了第二个成员变量Desc
总结: block的本质就是封装了函数调用以及函数调用环境的OC对象
block的调用是通过找到impl中的FuncPtr来获取到__main_block_func_0函数的地址, 然后调用, 同时传入__main_block_impl_0的地址

- 这里之所以直接使用
block->FuncPtr, 而不是block->impl.FuncPtr, 是因为impl是struct __main_block_impl_0的第一个成员变量, 所以impl的地址和block的地址相同

- 所以就可以通过
block的指针直接使用FuncPtr

三、带参数的block的底层结构
- 定义block时, 可以传入参数

- 使用终端
cd到main.m文件所在文件夹, 并执行下述命令行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- 将生成的
main.cpp文件拖到项目中并打开, 可以看到编译后的main函数, 可以看到block的定义和调用

- 删除类型转换后, 代码如下, 调用
block时传入了10和20

四、block的变量捕获
- 在OC中变量的类型主要使用三种, 分别是
auto、static、全局变量, 其中auto和static修饰的是局部变量 - 对这三种类型的变量, block在使用使用时, 会有不同的捕获方式
1、auto变量捕获(值捕获)
- 在OC中, 我们定义的变量, 默认就是
auto类型, 离开作用域就会销毁 - 当
block中使用外界的变量时, 就会进行变量捕捉

- 使用终端
cd到main.m文件所在文件夹, 并执行下述命令行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- 将生成的
main.cpp文件拖到项目中并打开, 可以看到编译后的block - 可以发现
__main_block_impl_0结构体中多了一个成员变量age, 这个age就是根据main函数中age生成的成员变量 __main_block_impl_0中的age和main函数中的age是独立的两个变量

block调用时使用的age是__main_block_impl_0的成员变量, 而不是main函数中的age

- 而
age的值, 是在block创建时传入的

- 所以在
main函数中, 如果在block定义后修改age的值, 在调用block时,age的值不会改变

- 此时底层结构如下

2、static变量捕获(指针捕获)
- 有代码如下, 使用
static修饰age变量

- 底层的
__main_block_impl_0中的age类型是指针类型int *

- 说明使用
static修饰过的变量, 会把变量的地址捕获到__main_block_impl_0中 - 此时
main函数中, 调用__main_block_impl_0的构造函数时, 传入的就是age的地址

- 在
block调用时,__main_block_func_0函数中也是通过age的地址访问age的值

- 此时如果在
main函数中block定义的后面修改age的值, 那么在block中通过地址访问的age就是修改之后的值

- 此时底层的调用代码如下

三、block中使用全局变量(不会捕获)

- 此时
block的底层结构如下,block并没有捕获age, 而是直接使用

总结:
block中如果使用了全局变量, 那么这个全局变量不会被捕获到block中
block调用时, 直接使用全局变量, 所以全局变量的值改变,block中使用的值也会相应改变
- 修改
age的值, 在调用block, 可以看到block中打印的值也发生了变化

四、block对auto、static、全局变量捕获方式

总结:
在block中, 如果使用局部变量, 那么就会捕获该变量
在block中, 如果使用全局变量, 那么就不会捕获该变量, 而是直接使用