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,如需轉載請自行聯系原作者