block(二)-- 底层分析
block(一):https://www.jianshu.com/p/b83574a8ef80
连接上文拿到block 的C++ 文件,内容如下:
那么我们第二章就慢慢分析这个C++文件。
__block 修饰的局部变量变成一个__Blcok_byref_c_0 的结构体,让我们看看这个结构体的实现:
-
我们可以很清楚的看到有一个 __frowatding 这个变量,它有什么用呢?
它是一个桥梁,在Block 内部,先找到这个结构体,通过这个结构体的frowatding成员变量找到外部的局部变量 -
那么__frowatding在不同类型的Block指向有什么不同呢:
这样通过__forwarding,无论是在block内部还是外部,不管是堆上还栈,都能访问到同一个__block修饰的变量。
__testBlockA_block_func_0(是Block 内部的方法(代码块))
block 实现方法(__testBlockA_block_impl_0),入参中有2个默认参数(方法): __testBlockA_block_func_0和__main_block_desc_0_DATA
结论:
1. 局部变量不能在block 里边修改值,__block修饰的和全局变量可以。
2. 验证了__forwarding在__block 结构体的指向(上边__forwarding的指向结论)
另一参数(方法): __main_block_desc_0_DATA
我们可以看到__main_block_desc_0中存储着两个参数:reserved
和Block_size;并且reserved赋值为0而Block_size则存储着__main_block_impl_0的占用空间大小。最终将__main_block_desc_0结构体的地址传入__main_block_func_0中赋值给Desc。
让我们在看看__testBlockA_block_impl_0 主方法里边的实现:
首先我们看一下__block_impl第一个变量就是__block_impl结构体。 来到__block_impl结构体内部
我们可以发现__block_impl结构体内部就有一个isa指针。因此可以证明block本质上就是一个oc对象。而在构造函数中将函数中传入的值分别存储在__main_block_impl_0结构体实例中,最终将结构体的地址赋值给block。
接着通过上面对__main_block_impl_0结构体构造函数三个参数的分析我们可以得出结论:
- __block_impl结构体中isa指针存储着&_NSConcreteStackBlock地址,可以暂时理解为其类对象地址,block就是_NSConcreteStackBlock类型的。
- block代码块中的代码被封装成__main_block_func_0函数,FuncPtr则存储着__main_block_func_0函数的地址。
- Desc指向__main_block_desc_0结构体对象,其中存储__main_block_impl_0结构体所占用的内存。
那么block是怎么执行的?
通过上述代码可以发现调用block是通过block找到FunPtr直接调用,通过上面分析我们知道block指向的是__main_block_impl_0类型结构体,但是我们发现__main_block_impl_0结构体中并不直接就可以找到FunPtr,而FunPtr是存储在__block_impl中的,为什么block可以直接调用__block_impl中的FunPtr呢?
重新查看上述源代码可以发现,(__block_impl *)block将block强制转化为__block_impl类型的,因为__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。
上面我们知道,FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。并且回头查看__main_block_func_0函数,可以发现第一个参数就是__main_block_impl_0类型的指针。也就是说将block传入__main_block_func_0函数中,便于重中取出block捕获的值。