如果大家还有映像的话,我们在前面讲解结构体 _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类型,所以都被分配在堆上,所以第二种情况没有问题。
网友评论