block的那些事兒,block事兒
這裡的部落格已經不怎麼維護了,建議你來這裡看我寫的部落格。如果發現部落格寫的有問題,歡迎指正。如果希望討論iOS技術,歡迎加我的QQ群:159974494
2015年3月2日,新年的第二篇部落格。
這篇部落格適合對block有一定的瞭解的人閱讀,如果你對還不知道什麼是block,那也許這篇文章更適合你。
block實現
假設你對Block已經有了一定瞭解。首先對於block,我們看看他到底是怎麼實現的。
int main() { __block id obj = [NSObject new]; void (^blk)(void) = ^{ NSLog(@"%@",obj); }; return 1;}
對於這樣一個block,我們使用clang工具將a.m(代碼的檔案)編譯為C語言
clang -rewrite-objc a.m
找到a.cpp檔案,裡面的代碼有很多。首先我們來看block的定義
/** block 對象(結構體) */struct __main_block_impl_0 { void *isa; //Class指標,指向block的Class int Flags; //用於儲存block引用計數,是堆上還是棧上或者全域區,是否有拷貝函數等資訊 int Reserved; //保留變數 void *FuncPtr; //函數指標,指向block的實現 struct __main_block_desc_0* Desc; //block的附加資訊 __Block_byref_obj_0 *obj; //捕獲的變數};
這個裡面有兩個地方不是太清楚,__main_block_desc_0和__Block_byref_obj_0,下面我們來看看
/** block的附加資訊 */static struct __main_block_desc_0 { size_t reserved; //保留變數 size_t Block_size; //block 的大小 // copy和dispose函數。用於將block中捕獲的對象copy到堆上和釋放。 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*);}
我們可以看到__main_block_desc_0是一個結構體,用於儲存block的附加資訊
我們再來看看__Block_byref_obj_0是個什麼東東?
struct __Block_byref_obj_0 { void *__isa; //Class指標 __Block_byref_obj_0 *__forwarding; //保證block複製到堆上時,原來棧上block也能訪問到堆上的block int __flags; //使用位儲存一些資訊,包括捕獲的對象在棧上還是堆上 int __size; //結構體大小 //記憶體管理用,copy和dispose void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); id obj; //捕獲的對象};
當我們對一個變數使用__block參數時,編譯器會自動將變數轉換成__Block_byref_obj_0對象。下面我們來看看__Block_byref_obj_0對象的具體細節
對於__forwarding指標說的不是太清楚,下面我來單獨說說。
當block產生時,__forwarding指向自身
當block從棧上copy到堆上時,棧上的__Block_byref_obj_0的__forwarding指向堆上的__Block_byref_obj_0。堆上的__Block_byref_obj_0的__forwarding指向自身
當block從棧上copy到堆上後,可能棧上的block和堆上的block同時都在使用(某些地方只能拿到棧上的block),如果直接使用__Block_byref_obj_0本身,將會導致資料不同步的問題,所以代碼所有地方都不直接使用__Block_byref_obj_0,而是使用__Block_byref_obj_0的__forwarding指標。
另外對於Block的捕獲關係希望大家弄清楚:
1. block(__main_block_impl_0)捕獲__block(__Block_byref_obj_0)obj
2. __block(__Block_byref_obj_0)捕獲對象id obj
block的一些問題1.block有幾種類型
block主要分為三種Block:
2.block copy做了什嗎?
根據block不同類型,copy分為三種情況:
3.block的循環參考怎麼避免4.非ARC下,__block為什麼能避免循環參考
首先我們來看迴圈應用的原因。一般情況下,self持有block,block又持有self,照成循環參考。iOS中的持有是指引用計數+1.
1. 由於block是self的屬性,block產生時,必然會引用計數+1,所以self持有block。
2. 由於block要使用必須要copy到堆上,否則棧上block誇地區使用會崩潰。block對象中捕獲了self作為屬性,當block copy到堆上時,調用block調用__main_block_desc_0中的copy函數,使self引用計數+1.所以block持有self
當使用__block屬性時,block捕獲__Block_byref_obj_0對象,__Block_byref_obj_0對象捕獲self。block copy時,使得__Block_byref_obj_0對象的retainCount+1,block持有__Block_byref_obj_0對象,然後self的引用計數不會增加,__Block_byref_obj_0對象不會持有self。所以block不持有self,打破循環參考
5.ARC下,__block為什麼不能避免循環參考
__block id blockSelf = self; //① 這裡是block持有self的關鍵void (^blk)(void) = ^{ //② ARC不會釋放blockSelf NSLog(@"%@",blockSelf);};
我們看上面的代碼,在ARC下,由於blockSelf預設為__strong類型,所以①位置處retainCount+1,blockSelf持有self。而block中捕獲了blockSelf,只要block存在,系統就不會釋放blockSelf。間接導致block持有self。
非ARC下由於①處不會使retainCount+1,所以能避免block持有self。
參考
唐巧大神的部落格
Objective-C進階編程