block概要
概念:block是带有自动变量的匿名函数
block模式
1. 语法:
^
返回值类型(可省略)`
参数列表表达式`
1 | ^ int (int count) { |
2. block类型变量
当函数参数与返回值使用block类型时,记述方式比较复杂,可以用 typedef 来解决
typedef int (^block_t)(int);
void func(block_t block)
block_t func()
3.自动截获变量
1 | int main(int argc, const char * argv[]) { |
打印的出来的是 0
block表达式截获所使用的自动变量的值,即保存自动变量的瞬时值
4. __block修饰符
block只能截获自动变的瞬时值,但不能给自动变量赋值
使用__block修饰符的自动变量,可以在block表达式中被赋值 自动变量截获没有实现对C语言数组的截获, 使用时编译器会报错
block实现
编译前源码
1 | int main(int argc, const char * argv[]) { |
编译后源码
1 |
|
先来看看 __main_block_func_0 的定义
1 | static void __main_block_func_0(struct __main_block_impl_0 *__cself) { |
那 main_block_impl_0 又是什么呢 ,看一下声明 main_block_impl_0是一个结构体,有三个成员
- struct block_impl impl // 结构体 block_impl 应该是block的本质
- struct __main_block_desc_0 Desc
- int a
1 |
|
再看看结构体 __block_impl 的定义,四个成员
- isa指针
- Flags
- Reserved
- 函数指针FuncPtr,函数指针指向源码编译后生成的函数,也就是__main_block_func_0
1 | struct __block_impl { |
再回头看看__main_block_impl_0 的构造函数:
主要是 给block_impl赋值,以及截获自动变量。可以看到block_impl的isa指针被赋值为_NSConcreteStackBlock
1 | __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { |
结构体main_block_impl_0还有一个成员没有分析到: struct main_block_desc_0* Desc;
1 |
|
根据成员变量和构造函数,很容易看出,只要是描述了 __main_block_impl_0 的实例大小
至此,所有相关的结构体都分析了,回过头来看看,编译后的源码
再贴一下,翻回去再翻回来挺累的,去掉了转换
1 | int main(int argc, const char * argv[]) { |
这就很清楚了,其实就是编译器生成了FuncPtr函数和block结构体,结构体值引用了自动变量(所以说是截获自动变量),调用的时候执行下FuncPtr。还有一个问题 _NSConcreteStackBlock 是个什么类?
__block 说明符
block中给变量赋值编译会报错。两种情况下允许在block中进行赋值操作。
- 全局变量、静态全局变量和静态变量
- __block 说明符修饰的变量
全局变量与全局静态变量,在block中是直接使用
静态变量则是是用静态变量的指针进行访问
__block 说明符编译后
编译前
1 | int main(int argc, const char * argv[]) { |
编译后
1 | int main(int argc, const char * argv[]) { |
主要变化在main_block_impl_0 结构体,变量a 不在是一个int 而是Block_byref_a_0 结构体
1 | struct __main_block_impl_0 { |
来看看__Block_byref_a_0
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 3};
1 | struct __Block_byref_a_0 { |
可以看到结构体中的__forwarding 就是指向了自身 int a 被初始化为3
放一张图来说明
block的存储域
问题:
- block超出变量作用域可以存在的原因
答案:block从栈上复制到了对堆
- forwarding的作用是什么
答案:forwarding保证不管block被分配的堆上还是在栈上,都能被访问到