iOS SDK 4.0開始映挂,Apple引入了block這一特性。趁最近比較閑,來(lái)研究一下block底層實(shí)現(xiàn)方式卖漫。先來(lái)看一段簡(jiǎn)單的代碼
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;
}
在上面代碼中創(chuàng)建了一個(gè)帶有兩個(gè)參數(shù)的block并調(diào)用它。將代碼保存為 main.m文件使用命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
將代碼轉(zhuǎn)換為底層c++赠群。會(huì)生成一個(gè)main.cpp文件羊始。包含大約3w行代碼。直接看最后面關(guān)鍵部分:
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函數(shù)里查描,我們可以看到block變量創(chuàng)建方式突委。聲明block是一個(gè)指向int (*)(int,int)
類型的函數(shù)指針。內(nèi)部的值為
((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
看起來(lái)很頭疼冬三。我們來(lái)一步一步分解匀油。
首先發(fā)現(xiàn)調(diào)用了一個(gè)函數(shù)
__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
而 __main_block_impl_0 是什么東西呢,翻看main函數(shù)上面的代碼我們可以發(fā)現(xiàn) __main_block_impl_0是一個(gè)結(jié)構(gòu)體勾笆,內(nèi)部為
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;
}
};
也就是說(shuō) 我們的block本質(zhì)上其實(shí)是一個(gè)指向 __main_block_impl_0結(jié)構(gòu)體的指針敌蚜。
該結(jié)構(gòu)體里面有兩個(gè)變量:
- 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 里面有一個(gè)isa指針。大膽猜測(cè)其實(shí)block本質(zhì)也是一個(gè)Objective-C對(duì)象窝爪。
__main_block_desc_0里面儲(chǔ)存著__main_block_impl_0變量的大小也就是block變量所占的內(nèi)存弛车。
構(gòu)造該結(jié)構(gòu)體時(shí)傳入了兩個(gè)參數(shù)齐媒。第一個(gè)參數(shù)為__main_block_func_0再翻看上面代碼我們發(fā)現(xiàn)** __main_block_func_0**是一個(gè)函數(shù)
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
return a+b;
}
對(duì)比一下,發(fā)現(xiàn)函數(shù)里面實(shí)現(xiàn)跟我們?cè)綽lock內(nèi)部實(shí)現(xiàn)是一樣的纷跛。所以這個(gè)函數(shù)應(yīng)該就是block本體喻括。這個(gè)函數(shù)指針被傳入了** __main_block_impl_0**結(jié)構(gòu)體里面的 impl 中,被 FuncPtr 變量持有贫奠。而第二個(gè)參數(shù)大概是一些描述信息唬血。
到了調(diào)用時(shí)我們的調(diào)用被轉(zhuǎn)換成了:
((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);
又是一連串的類型轉(zhuǎn)換。我們慢慢來(lái)抽:
從括號(hào)里面大致可以看出
((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)
是一個(gè)函數(shù)頭唤崭,后面是傳入?yún)?shù)
繼續(xù)分析發(fā)現(xiàn)實(shí)質(zhì)上是調(diào)用了
block->FuncPtr
這個(gè)函數(shù)刁品,而通過(guò)前面我們得知FuncPtr函數(shù)實(shí)質(zhì)上就是我們包裹在block中的代碼
這里有一個(gè)問題,我們知道block是一個(gè)__main_block_impl_0類型的對(duì)象浩姥。而這個(gè)類型里面直接拿不到FuncPtr的挑随。要通過(guò)impl才能拿到的。那為什么這里可以這么寫呢勒叠。
我們知道C語(yǔ)言(包括C++)結(jié)構(gòu)體本質(zhì)是直接按照先后順序(整齊)排列在內(nèi)存中的兜挨。而取結(jié)構(gòu)體內(nèi)部的本質(zhì)就是內(nèi)存偏移。impl變量是__main_block_impl_0類型的第一個(gè)變量眯分。所以impl的初始內(nèi)存地址就是block的初始內(nèi)存地址拌汇。FuncPtr在block中偏移位置與在impl中偏移位置是一樣的。所以可以通過(guò)類型轉(zhuǎn)換
(__block_impl *)block)->FuncPtr
直接獲取FuncPtr這個(gè)函數(shù)調(diào)用弊决。
以上