从源码、ARC、MRC带你理解block的三大类型

1,657 阅读5分钟

首先,在了解block三大类型之前,我们需要了解一个知识:

(温馨提醒:如果我的之前博客你没有看,有些概念你不清楚的话,你可能很难理解,如果前面你都看了,这篇博客你看就像切菜一样简单!)

程序的内存分配

一个由C/C++编译的程序占用的内存分为以下几个部分

栈(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。

全局区(静态区static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。

文字常量区:常量字符串放在这里, 程序结束后由系统释放

程序代码区:存放函数体的二进制代码。

相信大家对上面的这些内存分配的知识点都已经非常清楚,那我们就看一下

block的类型

block是有3种类型,通过调用class的方法或者isa指针查看具体的类型,最终都是继承NSBlock类型

接下来我们就看看,block就是一个oc对象,那我们就可以按照oc的,直接调用class就知道它的类型了

这里可以看出我前面说的,block就是oc对象,而这个NSGlobalBlock是继承NSBlock的,其他的种类都是继承NSBlock,等说完了,大家可以用这个方法试试其它2种block的的父类.

上面就是block的总共的三种类型:NSGlobalBlock、NSStackBlock、NSMallcBlock,

,这是运行时输出的结果,其实编译的结果可能有点不一样,那我们就把这份文件转成c++文件,这个我之前的博客都提了很多次,这次我就不细说了(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp)

其实你会发现,编译以后生成的都是NSConcreteStackBlock类型,首先我们肯定以运行时为准,因为编译过程还有个运行,运行就会用runtime动态修改你的东西;还有其实用clang转成c++的代码,有时候并不一定就是最终的代码,其实差别是不大,其实在llvm大概6.0以前都是一样的,现在会生成一个中间文件,还有就是程序运行中也是比较准确,如果有同学查看源码感觉不一样,不要很惊讶,这个很正常,可以参考,差别不大.

我们继续看

而GlobalBlock是在数据区域,MallocBlock是在堆区,StackBlock是在栈区,我这里不说堆、栈、数据区域的区别了,大家这个知识点如果不懂可以去查阅一下它们的区别,不然后面的很难理解

什么样的类型的是GlobalBlock?什么样的类型是MallocBlock?什么样的类型是StackBlock?

先看我的总结,我们再细细看

auto变量大家清楚吧?auto变量就是它所在的区域执行完毕,自动销毁的变量.如果不知道请看我上一篇博客,介绍的非常清晰.接下来我们就一个一个看每种类型的block

GlobalBlock类型

请看下面的代码

访问static的局部变量,全局变量a,都不是auto变量,所以上面3种情况都是GlobalBlock,因为GlobalBlock我们实际中用的非常少,所以这个也没有过多的需要阐述的

StackBlock类型(重点)

按照我们表格上面说的访问了auto变量的block就是StackBlock,那我看一下下面的代码:

ARC下运行的结果

应该有同学注意,结果和我们上面的结论不一样,其实是因为这环境是ARC,ARC下其实帮你做了很多事(这个为什么会这样,这个内容还是有点多,后面的博客,我会细说,你先不管这个为什么是这样),所以我们改成MRC才能看到它的本质(直接把Objective-C Automatic Reference Counting 设置为NO即可),我们再看下面

MRC下运行的结果

这就是StackBlock类型,下面我们再深入看一下这个StackBlock一个特殊情况,如下图

MRC下运行的结果

注意到没有,这个age的值变得很奇怪.这是因为:test()函数的作用域执行结束以后,它的作用域中的栈上面的变量就会销毁,比如里面的block就是StackBlock,所有test()执行完毕以后,block已经被释放了,导致访问内部就会出现混乱的情况.整个block内存被释放了里面的所有都会出现混乱

那上面的怎么解决这种问题呢???

答案很明显,我们把block栈内存变成堆内存就行了哇!怎么变成堆block,是不是直接栈调用copy就能是堆block了,那我们再试试:

是不是就很容易解决了!可能有同学还有疑问,如果把GlobalBlock也来调用copy会是怎么样呢?这个大家可以自己验证,答案还是:GlobalBlock,如果把MallocBlock也来调用copy会是怎么样呢?这个大家可以自己验证,答案还是:引用计数加1

还有如果我们block访问一个对象,一个对象也是auto变量,所以也是一样的,这些我就不验证了,如有疑问可以评论区讨论.

MallocBlock类型

这个上面已经说的很清楚了

相信还有同学有疑问,刚刚我们是MRC下测试的,至于ARC为什么会是另一个结果,我后面的博客会说到!

接下来博客我会介绍堆MallocBlock的一些具体情况和使用,来继续探讨block

如果觉得我写得对您有所帮助,请关注我,我会持续更新😄