????????本文作者主要參考Kazuki Sakamoto,Tomohiko Furumoto著,黎華譯的《Objective-C高級編程:iOS與OS X多線程和內存管理》一書。在平時工作中遇到不少關于Block的相關困惑,于是在反復閱讀了該書籍后,決定總結一篇關于Block的相關文章。如果還想深入了解Block,作者建議直接閱讀上述書籍。由于作者水平有限,文章難免存在紕漏,懇求各位讀者不吝賜教。
????????Block變量捕獲只針對Block中使用的變量,沒有使用的不會追加到__xxx(外部函數名)_block_impl_x(序號,從0開始)C語言結構體中,也就是說不會被Block所捕獲。
一、值變量類型的捕獲
1、全局變量
????????不會捕獲,因為在Block變換的C語言函數中可以直接訪問到,并且沒有任何變化,可直接使用。
2、全局靜態(tài)變量
????????不會捕獲,因為在Block變換的C語言函數中可以直接訪問到,并且沒有任何變化,可直接使用。
3、局部靜態(tài)變量
????????捕獲,因為變量在Block變換的C語言函數作用域外部,會生成指針指向該變量,并保存在Block轉換的C語言結構體中。
4、普通局部變量
????????局部靜態(tài)變量的捕獲機制似乎也適用于局部變量的捕獲,但是官方為什么沒有這么做?
????????這里說一下自己的理解,1)因為普通的值類型的局部變量的初衷只是在局部的作用域使用,超出作用域就不想再被其他地方所訪問,因此也沒有必要對其進行捕獲。2)進行捕獲的目的是,在Block內部進行對其操作時,外部的變量也能得到相同修改。對于對象類型的普通局部變量而言,例如一個Array對象,在對其添加一個Object后,顯然外部Array也是會得到新增的,因為本質上它們指向同一片內存區(qū)域。而對于值類型的普通局部變量,如int類型,我們在Block內想修改它,唯一的手段就是修改它的值,但是這是不被允許的,需要加上__block修飾符,但加上__block修飾符之后,它就變成了__block結構體了,不再是普通的值類型了。
二、指針變量類型(對象)的捕獲
????????在Block結構體內部會生成相應的 指針變量(對象),如引用外部的array對象,在Block內部則生成 id __strong array的成員變量,這個成員變量的內存管理通過調用Block內部的__xxx_block_copy_x函數來持有外部的對象,通過__xxx_block_dispose_x來釋放對象。
????????所以,Block中使用附有__strong修飾符(默認生成的對象屬于strong類型)的局部變量的對象和復制到堆上的__block變量由于被Block所持有,因而會超出其變量作用域而存在。
????????注意:如果外界的對象被__weak修飾符修飾,那在Block結構體內部會生成相應的weak類型的對象,當外界的對象被釋放時,由于weak指針自動置nil的原因,Block結構體內部的對象被置nil,雖然對其可以進行任何的方法調用,不會導致程序崩潰,但都是沒有結果的。
三、__block對值變量的使用
????????生成__block結構體,__block結構體內部生成一個值類型成員變量,其值等于外界值變量的值。此時在Block內部進行修改時,會以如下形式進行:
__Block_byref_val_0 *val = __cself->val ;
(val->__forwarding->val) = 1;
????????通過__block結構體的__forwarding指針訪問自己(或者是堆上自己的拷貝)來改變__block結構體內部的值。
????????__block成員變量的內存管理通過調用Block內部的__xxx_block_copy_x函數來持有對象,通過__xxx_block_dispose_x來釋放對象。
四、__block對指針變量(對象)的使用
????????同樣會生成__block結構體,__block結構體內部生成一個對象類型成員變量,其引用外界的對象。
????????__block成員變量的內存管理通過調用Block內部的__Block_byref_id_object_copy_x函數來持有對象,通過__Block_byref_id_object_dispose_x來釋放對象。
????????注意:如果外界的對象被__weak修飾符和__block修飾,那在__block結構體內部會生成相應的__weak類型的對象,當外界的對象被釋放時,由于weak指針自動置nil的原因,__block結構體內部的對象被置nil,雖然對其可以進行任何的方法調用,不會導致程序崩潰,但都是沒有結果的。
五、__block變量的__forwarding指針
__Block_byref_val_0 *val = __cself->val;
? (val->__forwarding->val) = 1;
????????在Block內部,來獲取持有的__block變量,并通過上述方式修改變量的值,但是__forwarding指針的存在是不是看似多余呢?
????????__block變量內部的__forwarding指針可以實現無論__block變量配置在棧上還是堆上時都能正確地訪問__block變量。原因如下:

????????這里會出現一種情況
????????__block int val = 0;
????????void (^blk)(void) = [ ^{ ++val; } copy ];
????????++val;
????????blk();
????????NSLog(@“%d”,val);
????????打印結果:2
????????開始,__block變量和Block都在棧上面,當Block調用copy方法后,Block被復制到堆中,而其內部調用的__block變量也會從棧復制到堆并被Block持有,此時__block變量有棧上的變量和堆上的變量,棧上的__block變量會將成員變量__forwarding指針的值替換為復制到目標堆上的__block變量用結構提實例的地址。
????????這里曾經存在誤區(qū),又學習了一遍。此時可同時訪問棧上/堆上的__block變量,但最終訪問的都是同一處__block變量,如果沒有發(fā)生__block變量從棧到堆的拷貝,那么在Block內部/外部訪問__block變量都是屬于棧上的同一個__block變量,反之則是訪問堆上的同一個__block變量
????????具體原因如下:

????????這里附上書中的一段原話:通過該功能,無論是在Block語法中、Block語法外使用__block變量,還是__block變量配置在棧上或堆上,都可以順利地訪問同一個__block變量。
六、block從棧復制到堆的時機
????????在ARC有效時,大多數情況下編譯器會恰當的判斷,自動生成將Block從棧復制到堆上的代碼,有如下情況:
1、調用Block的copy實例方法時
2、Block作為函數返回值時或函數的參數中傳遞Block時
3、將Block賦值給附有__strong修飾符id類型的類或Block類型的成員變量時
4、在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時
????????不過有時候需要手動將Block從棧復制到堆上,此時我們使用“copy實例方法”。