In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.
翻译过来,闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。
本文主要介绍Objective-C语言的block在编译器中的实现方式。主要包括:
block的内部实现数据结构介绍
block的三种类型及其相关的内存管理方式
block如何通过capture变量来达到访问函数外的变量

对应的结构体定义如下:
通过该图,我们可以知道,一个block实例实际上由6部分构成:
isa指针,所有对象都有该指针,用于实现对象相关的功能。
flags,用于按bit位表示一些block的附加信息,本文后面介绍block copy的实现代码可以看到对该变量的使用。
reserved,保留变量。
invoke,函数指针,指向具体的block实现的函数调用地址。
descriptor, 表示该block的附加描述信息,主要是size大小,以及copy和dispose函数的指针。
variables,capture过来的变量,block能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
该数据结构和后面的clang分析出来的结构实际是一样的,不过仅是结构体的嵌套方式不一样。但这一点我一开始没有想明白,所以也给大家解释一下,如下2个结构体SampleA和SampleB在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息。
在Objective-C语言中,一共有3种类型的block:
_NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。
我们在下面会分别来查看它们各自的实现方式上的差别。
为了研究编译器是如何实现block的,我们需要使用clang。clang提供一个命令,可以将Objetive-C的源码改写成c语言的,借此可以研究block具体的源码实现方式。该命令是
我们先新建一个名为block1.c的源文件:
然后在命令行中输入<code>clang -rewrite-objc block1.c</code>即可在目录中看到clang输出了一个名为block1.cpp的文件。该文件就是block在c语言实现,我将block1.cpp中一些无关的代码去掉,将关键代码引用如下:
下面我们就具体看一下是如何实现的。__main_block_impl_0就是该block的实现,从中我们可以看出:
一个block实际是一个对象,它主要由一个 isa 和 一个 impl 和 一个descriptor组成。
在本例中,isa指向 _NSConcreteGlobalBlock, 主要是为了实现对象的所有特性,在此我们就不展开讨论了。
impl是实际的函数指针,本例中,它指向__main_block_func_0。这里的impl相当于之前提到的invoke变量,只是clang编译器对变量的命名不一样而已。
descriptor是用于描述当前这个block的附加信息的,包括结构体的大小,需要capture和dispose的变量列表等。结构体大 小需要保存是因为,每个block因为会capture一些变量,这些变量会加到__main_block_impl_0这个结构体中,使其体积变大。在 该例子中我们还看不到相关capture的代码,后面将会看到。
我们另外新建一个名为block2.c的文件,输入以下内容:
用之前提到的clang工具,转换后的关键代码如下:
在本例中,我们可以看到:
本例中,isa指向_NSConcreteStackBlock,说明这是一个分配在栈上的实例。
main_block_impl_0 中增加了一个变量a,在block中引用的变量a实际是在申明block时,被复制到main_block_impl_0结构体中的那个变量a。因为这样,我们就能理解,在block内部修改变量a的内容,不会影响外部的实际变量a。
main_block_impl_0 中由于增加了一个变量a,所以结构体的大小变大了,该结构体大小被写在了main_block_desc_0中。
我们修改上面的源码,在变量前面增加__block关键字:
生成的关键代码如下,可以看到,差异相当大:
从代码中我们可以看到:
源码中增加一个名为__Block_byref_i_0 的结构体,用来保存我们要capture并且修改的变量i。
main_block_impl_0 中引用的是Block_byref_i_0的结构体指针,这样就可以达到修改外部变量的作用。
__Block_byref_i_0结构体中带有isa,说明它也是一个对象。
我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数。
在ARC开启的情况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。
我个人认为这么做的原因是,由于ARC已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。
希望本文能加深你对于block的理解。我在学习中,查阅了以下文章,一并分享给大家。祝大家玩得开心~
<a href="http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/">A look inside blocks: Episode 1</a>
<a href="http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/">A look inside blocks: Episode 2</a>
<a href="http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/">A look inside blocks: Episode 3</a>
<a href="http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html">对Objective-C中Block的追探</a>
<a href="https://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/Block_private.h">LLVM中block实现源码</a>
<a href="http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/">objective-c-blocks-quiz</a>
<a href="http://rypress.com/tutorials/objective-c/blocks.html">Blocks</a>
本文转自夏雪冬日博客园博客,原文链接:http://www.cnblogs.com/heyonggang/p/3604003.html,如需转载请自行联系原作者