小码哥iOS学习笔记第八天:|小码哥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变量
【小码哥iOS学习笔记第八天:|小码哥iOS学习笔记第八天: block的底层结构】

  • 底层的__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

    推荐阅读