基本知识点回顾
我们知道按照变量的作用域划分的话,变量可划分为局部变量
和全局变量
,而局部变量
又分为自动变量
和静态变量
,全局变量
分为静态全局变量
和非静态全局变量
1 |
|
Block的内存结构
新建一个命令行项目 mian.m
1 |
|
通过clang
命令
1 | xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m |
得到c++的代码,找到main
函数的实现为以下代码,
1 | int main(int argc, const char * argv[]) { |
__main_block_impl_0
相关的结构以及相关说明如下
1 | //__main_block_impl_0 |
对于__main_block_impl_0
结构体组成,类似于我们平时开发过程中把一些相关的东西封装成一个对象是一个道理的。
再回到main.m
函数的实现上:
myBlock
也就是保存了__main_block_impl_0
地址,__main_block_impl_0
初始化函数的第一个参数这里赋值了__main_block_func_0
函数其实就是那句NSLog(@"0000");
打印,__main_block_func_0
实现如下
1 | static void __main_block_func_0(struct __main_block_impl_0 *__cself) { |
而第二个参数就是__main_block_desc_0_DATA
就是对block
的描述,由于构造函数接受的指针类型,所以这里提供的是__main_block_desc_0_DATA
的地址
接下来改造下main.m
的代码
1 |
|
输出
1 | __NSGlobalBlock__ |
发现block
的类型的链是__NSGlobalBlock__
-> __NSGlobalBlock
-> NSBlock
-> NSObject
所以我们得出以下结论,block
的内存结构是一个结构体,并且也是一个oc对象;
内存结构图如下
Block调用
上面说的都是block
的申明,关于block
调用就是下面这行代码了
1 |
|
读者可能有疑惑既然block
是指向__main_block_impl_0
结构体的但是__main_block_impl_0
又没有FuncPtr
,那怎么可以直接调用呢,应该是block->impl.FuncPtr(block)
才对吧,由于__main_block_impl_0
是直接拥有了__block_impl
的并且处于第一个位置的,所以block
的地址也就是impl
的地址,所以可以这样写。
Block的类型
block
有3种类型
__NSGlobalBlock__
对应 _NSConcreteGloablBlock
;__NSStackBlock__
对应 _NSConcreteStackBlock
;__NSMallocBlock__
对应 _NSConcreteMallocBlock
;
在内存中存放的位置如图
接下来验证以下
1 | struct __main_block_impl_0 { |
由上面的源码中可以看到一个impl.isa
成员,看过我前面的文章的应该知道isa
是用来标识该对象是谁的实例的,为了更好的理解block
的类型,我们先把项目变成MRC
,将targets
-> Build Setting
-> Object-C Automatic Reference Counting
改为NO
MRC环境
再讲main
代码改为
1 | int a = 10; |
输出
1 | myBlock1:__NSGlobalBlock__ |
对于block9
:
不使用copy
则打印__NSStackBlock__
,并且MyPerson
正常释放;
使用copy
则打印__NSMallocBlock__
,MyPerson
无法正常释放;
得出以下规律:
block
内部没有访问auto
变量则为__NSGlobalBlock__
- 访问了
auto
变量则为__NSStackBlock__
, __NSStackBlock__
copy以后就会成为__NSMallocBlock__
__NSMallocBlock__
copy以后引用计数增加;__NSGlobalBlock__
copy以后什么也不做
对于block
进行copy操作
ARC环境
再把项目调回到arc
环境看看输出
1 | myBlock1:__NSGlobalBlock__ |
发现有变化的是myBlock5
和myBlock6
,这是因为在ARC
环境下,会根据一些特定的场景会自动调用copy
方法,规律如下
- 赋值给有
__strong
修饰的指针变量; - COcoa API 中有usingBlock的方法参数时;
- block作为GCD API方法入参时
- block作为函数返回值时
block
作为函数返回值
1 | typedef void (^myBlock) (void); |
读者可以切换是否为arc
来观看不同,其他情况无需做说明了 读者自行写demo测试吧
下篇将带来block
的值捕获,以及如何修改捕获的值