iOS SDK 4.0開始,Apple引入了block這一特性。趁最近比較閑,來研究一下block底層實現方式。先來看一段簡單的代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b = 11;
int (^block)(int,int) = ^(int a, int b) {
return a+b;
};
block(a,b);
}
return 0;
}
在上面代碼中創建了一個帶有兩個參數的block并調用它。將代碼保存為 main.m文件使用命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
將代碼轉換為底層c++。會生成一個main.cpp文件。包含大約3w行代碼。直接看最后面關鍵部分:
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 int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
return a+b;
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
int b = 11;
int (*block)(int,int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);
}
return 0;
}
在main函數里,我們可以看到block變量創建方式。聲明block是一個指向int (*)(int,int)
類型的函數指針。內部的值為
((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
看起來很頭疼。我們來一步一步分解。
首先發現調用了一個函數
__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
而 __main_block_impl_0 是什么東西呢,翻看main函數上面的代碼我們可以發現 __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;
}
};
也就是說 我們的block本質上其實是一個指向 __main_block_impl_0結構體的指針。
該結構體里面有兩個變量:
- struct __block_impl impl
- struct __main_block_desc_0 * Desc
__block_impl 和 __main_block_desc_0 類型 搜索上面代碼可以找到類型定義:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
可以看到 __block_impl 里面有一個isa指針。大膽猜測其實block本質也是一個Objective-C對象。
__main_block_desc_0里面儲存著__main_block_impl_0變量的大小也就是block變量所占的內存。
構造該結構體時傳入了兩個參數。第一個參數為__main_block_func_0再翻看上面代碼我們發現** __main_block_func_0**是一個函數
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
return a+b;
}
對比一下,發現函數里面實現跟我們原本block內部實現是一樣的。所以這個函數應該就是block本體。這個函數指針被傳入了** __main_block_impl_0**結構體里面的 impl 中,被 FuncPtr 變量持有。而第二個參數大概是一些描述信息。
到了調用時我們的調用被轉換成了:
((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);
又是一連串的類型轉換。我們慢慢來抽:
從括號里面大致可以看出
((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)
是一個函數頭,后面是傳入參數
繼續分析發現實質上是調用了
block->FuncPtr
這個函數,而通過前面我們得知FuncPtr函數實質上就是我們包裹在block中的代碼
這里有一個問題,我們知道block是一個__main_block_impl_0類型的對象。而這個類型里面直接拿不到FuncPtr的。要通過impl才能拿到的。那為什么這里可以這么寫呢。
我們知道C語言(包括C++)結構體本質是直接按照先后順序(整齊)排列在內存中的。而取結構體內部的本質就是內存偏移。impl變量是__main_block_impl_0類型的第一個變量。所以impl的初始內存地址就是block的初始內存地址。FuncPtr在block中偏移位置與在impl中偏移位置是一樣的。所以可以通過類型轉換
(__block_impl *)block)->FuncPtr
直接獲取FuncPtr這個函數調用。
以上