小码哥iOS学习笔记第八天: block的底层结构

一、最简单的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的底层结构

  • 使用终端cdmain.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, 是因为implstruct __main_block_impl_0的第一个成员变量, 所以impl的地址和block的地址相同

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

三、带参数的block的底层结构

  • 定义block时, 可以传入参数

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

  • 删除类型转换后, 代码如下, 调用block时传入了1020

四、block的变量捕获

  • 在OC中变量的类型主要使用三种, 分别是auto、static、全局变量, 其中auto和static修饰的是局部变量
  • 对这三种类型的变量, block在使用使用时, 会有不同的捕获方式

1、auto变量捕获(值捕获)

  • 在OC中, 我们定义的变量, 默认就是auto类型, 离开作用域就会销毁
  • block中使用外界的变量时, 就会进行变量捕捉

  • 使用终端cdmain.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中的agemain函数中的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中, 如果使用全局变量, 那么就不会捕获该变量, 而是直接使用

转载于:https://juejin.im/post/5c46e4975188252620583ef5

你可能感兴趣的:(小码哥iOS学习笔记第八天: block的底层结构)