《Objective-C高階程式設計》讀書筆記--2.3.1--Blocks的實質
前言
Blocks的原理,每當自己對知識體系有一定提升之後,再回過頭來看一下曾經讀過的書籍,會發現對它的理解逐步加深。藉著讀書筆記活動,立個小目標,把Block徹底搞明白,重讀《Objective-C高階程式設計 iOS與OS X多執行緒和記憶體管理》第二章節block原理部分,一方面給自己做個筆記,另一方面加深以下印象。
block實質
block程式碼:
void (^blk)(void) = ^ {
printf("Block");
};
blk();
複製程式碼
執行xcrun -sdk iphonesimulator clang -rewrite-objc 原始碼檔名
main.cpp
的檔案,開啟很恐怖,六萬多行...
實際上和block相關的程式碼在最後幾十行:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf ("Block");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製程式碼
這就是我們一直在使用的block,因為都是struct結構看上去有點抽象,不過理解起來並不難。
首先先從__main_block_func_0
函式開始,因為我們想要執行的回撥看原始碼都是寫在這個函式裡面的,block使用的匿名函式(也就是我們定義的block)實際上被作為簡單的C語言函式(block__main_block_func_0
)來處理,該函式的引數__cself相當於OC例項方法中指向物件自身的變數self,即__self為指向Block值的變數。__self與OC裡面的self相同也是一個結構體指標,是__main_block_impl_0
結構體的指標,這個結構體宣告如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製程式碼
第一個變數是impl
,也是一個結構體,宣告如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製程式碼
先看FuncPrt
,這個就是block
括號中函式的函式指標,呼叫它就能執行block括號中的函式,實際上在呼叫block的時候就是呼叫的這個函式指標,執行它指向的具體函式實現。 第二個成員變數是Desc
指標,以下為其__main_block_desc_0
結構體宣告:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
複製程式碼
其結構為今後版本升級所需的區域和Block的大小。 實際上__main_block_impl_0
結構體展開最後就是這樣:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
};
複製程式碼
還定義了一個初始化這個結構體的建構函式:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
複製程式碼
這就是整個__main_block_impl_0
結構體所包含的,既然定義了這個結構體的初始化函式,那在詳細看一下它的初始化過程,實際上該結構體會像下面這樣初始化:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
複製程式碼
__main_block_func_0
這不就是上面說到的那個指向函式實現的那個函式指標,也就是說只需要呼叫到結構體裡面的FuncPtr
就能呼叫到我們的具體實現了。那這個建構函式在哪裡初始化的,看上面的原始碼是在我們定義block的時候:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
複製程式碼
簡化為:
struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
複製程式碼
該原始碼將__mian_block_impl_0
結構體型別的自動變數,即棧上生成的__mian_block_impl_0
結構體例項的指標,賦值給__mian_block_impl_0
結構體指標型別的變數blk。聽起來有點繞,實際上就是我們最開始定義的blk
為__main_block_impl_0
結構體指標指向了__main_block_impl_0
結構體的例項。
接下來看看__main_block_impl_0
結構體例項的構造引數:
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
複製程式碼
第一個引數為由Block語法轉換的C語言函式指標,第二個引數是作為靜態全域性變數初始化的__main_block_desc_0
結構體例項指標,以下為__main_block_desc_0
結構體例項的初始化部分程式碼:
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
複製程式碼
即__main_block_impl_0
結構體例項的大小。
接下來看看棧上的__main_block_impl_0
結構體例項(即Block
)是如何根據這些引數進行初始化的。也就是blk()
的具體實現:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
複製程式碼
簡化以下:
(*blk->impl.FuncPtr)(blk);
複製程式碼
FuncPtr
正是我們初始化__main_block_desc_0
結構體例項時候傳進去的函式指標,這裡使用這個函式指標呼叫了這個函式,正如我們剛才所說的,有block語法轉換的__main_block_func_0
函式的指標被賦值成員變數FuncPtr
中。blk
也是作為引數進行傳遞的,也就是最開始講到的__cself
。到此block
的初始化和呼叫過程就結束了。
待更新...