本文內容主要來自于坂本一樹 / 古本智彥:Objective-C高級編程
Block - 帶有自動變量的匿名函數。
Block 的實質
Block 實質上是一個 Objective-C 對象。和其它對象一樣,Block 在內部是由一個結構體實現的。結構體中主要包含:
結構體成員 | 意義 |
---|---|
void *isa | isa 指針 |
int Flags | 標志位 |
void *FuncPtr | 函數指針,指向實現 Block 功能的 C 語言函數 |
unsigned long Block_size | Block 的大小 |
...... | 捕獲的變量 |
其中,void *FuncPtr 指針指向的函數參數中包含一個指向 Block 結構體自身的 self 指針。
- Block 的定義,就是定義一個表示這個 Block 的新的結構體。
- Block 的創建,就是調用這個結構體的構造函數。
- Block 的調用,就是以 Block 結構體為參數(之一),調用 Block 成員變量中的函數指針。
捕獲自動變量
在捕獲自動變量的情況下:
- Block 定義時,將捕獲的變量加入結構體。
- Block 創建時,使用被捕獲變量的值來初始化結構體(傳值)。
- Block 調用時,Block 內部的 C 函數會通過 self 指針訪問 Block 結構體成員中的變量(傳值)。
__block 說明符
Block 可以修改被捕獲的變量嗎?
正常情況下,Block 無法修改被捕獲的原始的自動變量,只能修改保存在自己內部的那一份。
Block 當然可以訪問并修改全局變量和靜態全局變量。
Block 也可以訪問并修改所屬作用域中的靜態變量。Block 在定義時,會將指向靜態變量的指針存入結構體使用,這樣就可以在靜態變量的作用域外正常使用它,并且修改它的值。
__block 變量的內部實現
__block 說明符是為了支持 Block 語法而新增加的存儲類說明符,用來指定可以被 Block 修改的自動變量。
帶有 __block 說明符的變量,在內部被實現為一個結構體,由這個結構體持有變量的值。
捕獲了 __block 變量的 Block 會把指向 __block變量(結構體)的指針放到自己的結構體中。
Block 存儲域
共有 3 種 Block:
- _NSConcreteGlobalBlock:建立在內存數據段的 Block。定義在全局作用域下的 Block 或者不捕獲任何自動變量的 Block 會將結構體的 isa 指針指向 _NSConcreteGlobalBlock。因為 _NSConcreteGlobalBlock 內部不保存任何狀態,不會變化,所以只需要一個實例。
- _NSConcreteStackBlock:捕獲自動變量的 Block,建立在棧上。
- _NSConcreteMallocBlock:建立在堆上。
當超出作用域時,建立在棧上的 Block 會被自動銷毀。為了避免銷毀,可以使用 copy
實例方法將 Block 復制到堆上。ARC 會自動管理堆上的 Block。
cocoa 框架中名字包含 usingBlock: 的方法,以及 GCD 中的 API,會自動將 Block 復制到堆上。其他情況下如果需要,應該手動復制 Block。(例如將棧上的 Block 加入到集合類中時。)
__block 變量存儲域
__block 變量建立在棧上,當使用 __block 變量的 Block 被復制到堆上時,__block 變量也會自動復制到堆上。
當 __block 變量被復制到堆上后,會被 Block 所持有。ARC 會自動管理堆上的 __block 變量。
Block 被復制到堆上的時機
以下情況,Block 會被自動復制到堆上:
- Block 作為函數返回值時。
- 將 Block 賦值給類的成員變量,變量類型為 id __strong 或者是 Block 類型時。
- 如前所述,在 cocoa 框架中名字包含 usingBlock: 的方法,以及 GCD 中的 API 中傳遞 Block 時。
注意:棧上的 Block 不會持有變量。如果 Block 要超出作用域使用作為自動變量的對象,一定要使 copy
方法將 Block 復制到堆上,除非前述 Block 會被自動復制的情況。 (否則對象會被提前銷毀。)