基本知识点回顾
我们知道按照变量的作用域划分的话,变量可划分为局部变量和全局变量,而局部变量又分为自动变量和静态变量,全局变量分为静态全局变量和非静态全局变量
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的值捕获,以及如何修改捕获的值