小码哥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的底层结构
- 使用终端
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在使用使用时, 会有不同的捕获方式
- 在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、全局变量捕获方式
总结:转载于:https://juejin.im/post/5c46e4975188252620583ef5
在block中, 如果使用局部变量, 那么就会捕获该变量
在block中, 如果使用全局变量, 那么就不会捕获该变量, 而是直接使用
推荐阅读
- 2020-04-07vue中Axios的封装和API接口的管理
- 哥哥,森亚fai|哥哥,森亚fai 络!
- iOS中的Block
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片
- 凭什么要对外卖小哥说谢谢()
- 哥伦布是如何发现新大陆的(美洲土著的两大文明又是如何沦陷的?)
- 我哥的打工经历
- 萌哥与辉哥(第六集)|萌哥与辉哥(第六集) | 小别离
- 2019-08-29|2019-08-29 iOS13适配那点事
- Hacking|Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)