一: block的原理是怎樣的?本質是什麼?
- block本質上也是一個OC物件,因為它的內部也有個isa指標
- block是封裝了函式呼叫以及函式呼叫環境的OC物件
接下來我們將通過底層原始碼來論證上訴兩點.
首先我們寫一個簡單的block,通過clang編譯器編譯成C++程式碼,檢視一下block的底層機構:
int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; int height = 10; //宣告block變數 void(^myBlock)(void) = ^{ NSLog(@"age is %d height is %d",age,height); }; //呼叫block myBlock(); } return 0; }
通過clang編譯器執行編譯成C++程式碼:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
clang編譯器編譯完後會得到一個.cpp格式的檔案,這就是我們剛才轉換的.m檔案的底層程式碼.我們開啟.cpp檔案,差不多有三萬多行程式碼,我們直接拖到最下面,找到block相關的程式碼.如下
一: block底層資料結構
// 一: block底層資料結構 struct __main_block_impl_0 { struct __block_impl impl; // 1: impl 結構體 struct __main_block_desc_0* Desc; // 2: block描述資訊的結構體 int age; //3:捕獲的外部變數 int height; //4: 和結構體同名的建構函式 ( C++語法 , 類似於 OC 的init方法,返回一個結構體物件,類似於返回self) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
通過底層程式碼我們可以看到,block在底層中的資料結構是一個結構體,這個結構體有四個部分組成:
1: struct __block_impl
2: struct __main_block_desc_0
3: 捕獲的外部變數
4:和block結構體同名的建構函式
我們找到struct __block_impl 結構體
:
//struct __block_impl 結構體 struct __block_impl { void *isa; //指向 block 的型別 int Flags;//按位表示block的附加資訊 int Reserved;//保留變數 void *FuncPtr; //封裝了執行 block 程式碼塊的函式地址 };
然後我們再找到struct __main_block_desc_0 結構體
:
static struct __main_block_desc_0 { size_t reserved;//保留變數大小 size_t Block_size;//block所佔用的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
這3個結構體之間的關係就像下圖這樣:
我們在網上看到的一張很經典的block底層結構的圖片就是把這兩個結構體成員列表彙總進去得到的:(descriptor結構體中的copy 和 dispose 我們後面會講到)
二: main函式 (block 捕獲區域性變數(auto變數)捕獲到內部 值傳遞)
// 1: main函式 int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; int height = 10; //宣告block變數 void(*myBlock)(int ,int) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, height)); //呼叫block ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); } return 0; }
這段底層程式碼中,宣告block和呼叫block的部分涉及了太多的型別轉換,不便於閱讀理解,我們去掉型別轉換的部分,簡化如下:
// : main函式 int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; int height = 10; //宣告block void(*myBlock)(void) = &__main_block_impl_0(//呼叫block資料結構中的同名的構造函 __main_block_func_0,//封裝了block要執行的程式碼塊的函式 &__main_block_desc_0_DATA,//block描述資訊的結構體 age,//把區域性變數當做引數傳入 height ); //呼叫block myBlock->FuncPtr(myBlock); } return 0; }
可以看到,我們在申明block的時候,呼叫block的建構函式,傳入了四個引數,分別是__main_block_func_0 , __main_block_desc_0_DATA , age , height
,我們對比著它的建構函式,看看它的內部都做了什麼:
// block底層資料結構 struct __main_block_impl_0 { struct __block_impl impl; // 1: impl 結構體 struct __main_block_desc_0* Desc; // 2: block描述資訊的結構體 int age;//3: 捕獲的外部變數 int height; //4: 同名的建構函式 ( C++語法 ) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height) { impl.isa = &_NSConcreteStackBlock; //isa 指向了block的型別 impl.Flags = flags;//按位表示block的一些附加資訊,這裡是0 impl.FuncPtr = fp; // 封裝了block要執行的程式碼塊的函式 Desc = desc; //block描述資訊的結構體 } };
- 首先,建構函式的內部會給
impl
結構體的isa
指標賦值,決定block的型別; - 然後,再把傳進來的
__main_block_func_0
傳給了fp
,又把fp
賦值給了impl
結構體的FuncPtr
,__main_block_func_0
這個函式指標指向封裝了block程式碼塊的函式地址; - 最後,再把傳進來的
__main_block_desc_0_DATA
結構體賦值給了Desc
;
其實這裡還隱藏了一個很重要的一步,只是底層程式碼上沒有體現出來:在建構函式的內部,會自動把傳遞進來_age
,_height
自動賦值給我們block內部的age
,height
,這就是block的捕獲機制.
我們來分別看一下__main_block_func_0
,__main_block_desc_0_DATA
這兩個結構體長什麼樣子:
- __main_block_func_0 結構體: block 真正要執行的程式碼塊
// 封裝了block要執行的程式碼塊的函式 static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_b680e8_mi_0,a,b); }
可以看到,就是我們.m中寫的很簡單的一句NSLog
語句.說明block的程式碼塊確實是封裝成了一個函式去執行的.
- __main_block_desc_0_DATA:
static struct __main_block_desc_0 { size_t reserved;//保留變數的大小 size_t Block_size;//block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
這個結構體中主要就是一個size_t Block_size
存放了block的大小.
三、總結:
-
block的底層就是一個
struct _block_impl_0
型別的結構體,這個結構體中包含一個isa
指標,所以從更深的層次來說,block就是一個OC物件. -
block結構體中又包含
impl
和Desc
結構體,impl
結構體中有一個非常重要的成員FuncPtr
,FuncPtr
是一個指標,指向了封裝了blcok程式碼塊的函式,
我們看到呼叫block的程式碼:myBlock()
的底層實際上是這樣子:myBlock->FuncPtr(myBlock);
,可以看出呼叫block其實就是呼叫了FuncPtr()這個函式 -
Desc
結構體存放了block的大小
參考連結:https://www.jianshu.com/p/e6759404f9cd