本文主要介绍block内部结构以及实现机制。
©原创文章,转载请注明出处!
block内部结构
ok,假设大家已经了解了block语法。
呵呵,讲解技术问题的两大法宝:源码+图,在此也不例外。
源码:
1 | // revised new layout |
最新的开源代码对Block_descriptor
做了优化:
1 | struct Block_descriptor_1 |
1 | static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock) |
其实也很简单,当block需要copy
、dispose
函数时,才在descriptor
中包含相应的函数指针(Block_descriptor_3
用于存储与ABI相关的metadata,具体细节不清楚)。
那么什么情况下block才需要copy
、dispose
函数呢?
简单来说,就是在block中引用了外部的OC对象或其他block时。后面会有更详细的讲解。
在上面这个例子中,由于blockA
没有引用外部对象,因此编译器不会为其合成copy
和dispose
函数。
而在这个例子中由于blockB
引用了blockA
,故编译器为blockB
合成了copy
和dispose
函数。
图:
最后,我们用clang验证一下(利用clang的-rewrite-objc命令可以将Objectvie-C源码转换为C语言源码):
1 | int main() |
利用clang的-rewrite-objc:clang -rewrite-objc 1.c
会生成相应的cpp文件:
1 | struct __block_impl |
嗯,已经很清楚了,block会被编译器转换为C语言的struct
。
额,熟悉的isa
指针又出现在了表示block的结构体中,通过《Objective-C Object Model》系列文章我们知道,含有isa
指针的struct,在Objective-C中都可以被认为是Object。
那么block里面的isa
指针指向谁呢?
上述代码17行就是答案所在:impl.isa = &_NSConcreteStackBlock;
,即block对应的类是_NSConcreteStackBlock
。
是的,根据block的不同,可以分为三种类型:_NSConcreteStackBlock
、_NSConcreteMallocBlock
以及_NSConcreteGlobalBlock
。
_NSConcreteMallocBlock
和_NSConcreteGlobalBlock
两种类型的block。其实不是这样的……
这是个ARC的例子,我们可以看到__weak
类型的block以及作为函数参数的block都是_NSConcreteStackBlock
类型!
之所以大家会误认为ARC下只有_NSConcreteMallocBlock
和_NSConcreteGlobalBlock
类型的block,是因为将block赋给strong
类型的变量时,编译器会自动将其copy到heap上!如上面的strongBlk
。
根据这三种block类型的名称,我们也能猜出它们在内存上的分布情况(来源):
_NSConcreteGlobalBlock
很明显,_NSConcreteGlobalBlock
类型的block分配在数据区域。以全局的方式定义的block,其类型为_NSConcreteGlobalBlock
。
注意:在函数内部定义的block,如果该block没有引用block外的任何自变量,那么该block的类型同样是_NSConcreteGlobalBlock
,并且存储在data section中。
因此,以下两种情况下生成的block为_NSConcreteGlobalBlock类型:
以全局的方式定义的block;
在block内没有引用block外的任何自变量。
_NSConcreteMallocBlock
_NSConcreteMallocBlock
类型的block存储在heap上,通常是通过copy函数将stack上的block copy到heap上。
_NSConcreteStackBlock
_NSConcreteStackBlock
类型的block存储在stack上,除了上述两种类型的block,其他block都属于_NSConcreteStackBlock
。