參考書籍:《Effective Objective-C 2.0》 《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》
根據(jù)書中所說,Block是“帶有自動(dòng)變量值的匿名函數(shù)”喉酌,下面算是我的讀書筆記,附上由clang編譯出來的過程代碼來理解Block的實(shí)質(zhì)赐俗。
首先介紹下clang,Clang是一個(gè)[C語言]弊知、[Objective-C]阻逮、C++語言的輕量級編譯器。---百度百科
1.相關(guān)clang命令
可能用到的關(guān)于clang的指令如下:
cd到main目錄下吉捶,執(zhí)行clang -rewrite-objc main.m指令夺鲜,可以將OC源碼轉(zhuǎn)換成C語言源代碼,但要注意這里轉(zhuǎn)換出來的C源碼并不是最終程序執(zhí)行的源碼呐舔,只是過程代碼币励,所以僅便于我們從更加底層的了解OC源碼。源碼無錯(cuò)且執(zhí)行完畢后同級目錄下便會出現(xiàn)main.cpp文件珊拼。
1.若要指定模擬器環(huán)境下運(yùn)行:
首先可執(zhí)行xcodebuild -showsdks查看本地裝有的SDK
然后執(zhí)行xcrun -sdk iphonesimulatorx.x clang -rewrite-objc main.m(x.x即為本機(jī)安裝的模擬器版本)
2.若指定真機(jī)運(yùn)行
xcrun -sdk iphoneosx.x clang -rewrite-objc main.m
3.若代碼中import了第三方的SDK食呻,可以通過下列命令關(guān)閉
xcrun -sdk iphonesimulator10.0 clang -rewrite-objc -F /Users/Desktop main.m
注意,在mac系統(tǒng)下澎现,無需先建工程再執(zhí)行源碼轉(zhuǎn)化仅胞,在任意目錄先新建一個(gè)m文件即可轉(zhuǎn)換。
2.Block源碼
先舉一個(gè)簡單的block剑辫,方便查看Block的結(jié)構(gòu)干旧。
OC源碼:
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^aBlock)() = ^{
NSLog(@"Hello world !");
};
aBlock();
}
return 0;
}
轉(zhuǎn)換后的相關(guān)C源碼如下:(轉(zhuǎn)換出來的源碼雖然有九萬多行,但大部分是因?yàn)槲覍?dǎo)入了Foundation框架妹蔽,以下只列出與main有關(guān)的部分源碼)
雖然書中已經(jīng)寫的很詳細(xì)了椎眯,但我這里重新理一遍理解的思路挠将,一方面是便于自己梳理要點(diǎn)便于記憶,另一方面有關(guān)于書中有些疑問编整,在這里記下來舔稀,方便日后交流與回顧。
下面根據(jù)上圖中的標(biāo)號順序來理解這段轉(zhuǎn)換成C源碼的實(shí)現(xiàn)原理:
1.先來看看一眼就能認(rèn)出的block內(nèi)容部分掌测,在OC中添加的block內(nèi)容為^{NSLog(@"Hello world !");};
在標(biāo)注1的地方正好能看到熟悉的NSLog内贮,所以可以看出靜態(tài)函數(shù)__main_block_func_0即對應(yīng)OC中block中執(zhí)行的函數(shù)部分。
2.接著來看main_block_func_0中傳遞的參數(shù)為 main_block_impl_0結(jié)構(gòu)體類型的 cself指針汞斧,這里cself就相當(dāng)于self(不做拓展介紹夜郁,更多的細(xì)節(jié)請自己查閱更加底層的資料)。然后來具體看__main_block_impl_0結(jié)構(gòu)體粘勒,在標(biāo)注2的地方即為這個(gè)結(jié)構(gòu)體的成員變量與構(gòu)造函數(shù)拂酣。
這個(gè)結(jié)構(gòu)體包含兩種變量與一個(gè)構(gòu)造函數(shù):
- __block_impl impl
- __main_block_desc_0* Desc;
- 構(gòu)造函數(shù)__main_block_impl_0
前兩種結(jié)構(gòu)體變量稍后講述,先看一下構(gòu)造函數(shù)main_block_impl_0中所傳遞的參數(shù)為void *fp仲义,struct __main_block_desc_0 *desc, int flags=0
其中fp即為指向block要實(shí)現(xiàn)的函數(shù)指針剑勾,desc為block的相關(guān)描述信息埃撵,直接賦給Desc變量,最后是帶默認(rèn)值的flags變量虽另。
3.先來看__main_block_impl_0結(jié)構(gòu)體:
- isa:指向?qū)嵗龑ο笤萘酰帽砻鰾lock也跟一般的OC對象類似,擁有isa指針捂刺,共有三種block類型:_NSConcreteStackBlock谣拣、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
- Flags :按位承載 block 的附加信息族展;
- Reserved:保留變量森缠;
- FuncPtr: 函數(shù)指針,指向 Block 要執(zhí)行的函數(shù)
4.再來看__main_block_desc_0:block 的相關(guān)描述信息結(jié)構(gòu)體
- reserved:結(jié)構(gòu)體信息保留字段
- Block_size:結(jié)構(gòu)體大小
- 結(jié)構(gòu)體類型變量: __main_block_desc_0_DATA
5.main函數(shù)部分
void (*aBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
這句能猜到就對應(yīng)著void (^aBlock)() = ^{NSLog(@"Hello world !");};
將其強(qiáng)制轉(zhuǎn)化等操作去掉后仪缸,就僅剩下了*aBlock = &__main_block_impl_0;
就可以看出這就是一個(gè)簡單的指針賦值贵涵,將main_block_impl_0結(jié)構(gòu)體賦值給aBlock指針。我的理解為通過構(gòu)造函數(shù)構(gòu)造出一個(gè)main_block_impl_0結(jié)構(gòu)體并賦給了一個(gè)指針恰画,這個(gè)即為block的c層面上的理解宾茂。
6.最后看main中的調(diào)用block部分:
OC: aBlock(); C: ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
這句代碼可簡化為*aBlock->impl.FuncPtr,即通過aBlock變量中的impl的FuncPtr函數(shù)指針調(diào)用函數(shù)。