block详解<3>: block在内存区域中是如何分布的

如果大家还有映像的话,我们在前面讲解结构体 _block_impl的时候,里面有一个成员叫isa,这个代表了block在内存区域中的分布。如果你看了一些关于block的文章,isa会有三种取值:

isa = &_NSConcreteStackBlock;
isa = &_NSConcreteMallocBlock;
isa = &_NSConcreteGlobalBlock;

但是clang出来的文件,里面都是第一种,说明这并不是block在内存中的真正分布。真正的分布,我们可以通过打印来确定。


屏幕快照 2016-09-22 2.35.54 PM.png
屏幕快照 2016-09-22 2.49.47 PM.png

第二张图的打印结果是NSMallocBlock,同时我又试了其他的几种情况,发现都是NSMallocBlock,说明在ARC环境下,栈上的block默认都会被拷贝到堆上,也就是说,在ARC环境下,block只有两种类型:NSGlobalBlock 和 NSMallocBlock。那么到底有没有特殊的情况呢?后来查查资料,发现还真有。

屏幕快照 2016-09-22 5.09.57 PM.png

执行上面的代码的时候,直接crash了,从错误的提示可以看出,是某个东西的内存过早的释放了。我们仔细观察一下控制台,发现数组的第一个元素是NSMallocBlock类型,这是被分配在堆上的block;数组的第二个元素有点儿问题:发现它的内存地址跟argv的格式比较像,而argv是函数的参数,内存是被分配在栈内存上的,所以第二个block也是被分配在栈内存上的,并没有被拷贝到堆内存上。然后我们换一种写法:

屏幕快照 2016-09-22 5.21.23 PM.png

发现可以正常运行,并且打印的结果也是我们想要的。

我们再看一下上面的数组的初始化函数

+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;

这个函数是一个可变函数,只有第一个参数被显示的申明为ObjectType类型,也就是id类型,其他的参数并没有被显示的申明为id类型。这也验证了第一种情况下第一个block被分配在堆上,第二个block被分配在栈上。而我们的第二种写法是,先申明一下block,在block到底是什么一文中,我们已经说了,block其实就是一个函数指针,也可以说它是一个id类型,所以在第二种写法下,两个block都被显示的申明为id类型,所以都被分配在堆上,所以第二种情况没有问题。

由此我们可以得出一个结论:block作为函数的参数时,一定要被显示的申明为id类型,才会被分配在堆上。
参考文章

ARC 下向 NSArray 添加 Block 元素的一个小坑

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容