iOS Block原始碼分析系列(一)————2分鐘明白Block究竟是什麼?
Block其實就是C語言的擴充功能,實現了對C的閉包實現,一個帶有區域性變數的匿名函式。Block的語法,型別介紹我這裡就不BB了,網上太多了,全是介紹怎麼寫的,這裡開幾篇部落格來看看block的原始碼和內部實現結構,網上寫的很亂很雜,而且都不全,自己買了本書,開搞!!!!!!
入門嘛,咱們先來一段最最最簡單的程式碼
#include "stdio.h"
int main()
{
void (^block)(void) = ^(void)
{printf("aaa\n");};
block();
return 0;
}
樓主你sb麼,這那麼簡單你看個球啊
別急,我們來看看Clang(LLVM)
通過clang
命令列工具中的-rewrite-objc
引數,我們可以把OC程式碼轉化為C++的實現。幫助文件中的說明是這樣的:Rewrite Objective-C source to C++。
clang-rewrite-objc檔名
以上命令會生成一個a.cpp檔案,其中就是a.c檔案的C++實現。越過開頭的冗餘程式碼,我們關心的程式碼部分往往在最下端。
不會用vim編寫程式碼的看看這個,兩分鐘搞定點選開啟連結
終端裡面clang -rewrite-objc mkj.c,然後就會生成cpp,轉換的原始碼就出現了
這個檔案開啟就會出現幾百行程式碼,這裡的程式碼很多都是宣告來的,總結下來就是如下程式碼
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 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("aaa\n");} 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() { void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; }
那兩行程式碼竟然增加到了那麼多,可以從裡面看出來,最終block匿名函式會用C語言的函式指標來處理
原始碼結構分析部分
1.block實際的結構體部分(本體)
首先impl和Desc也是兩個結構體,而__main_block_imp_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; } };
2.我們先來看看第一個成員變數impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
在objc中,id代表了一個物件。根據上面的宣告,凡是首地址是*isa的struct指標,都可以被認為是objc中的物件isa指標,這個很熟悉吧,這就能看出其實block其實就是物件
他的第一個屬性也是一個結構__block_impl,而第一個引數也是一個isa的指標。
在執行時,NSObject和block的isa指標都是指向物件的一個8位元組。
NSObject及派生類物件的isa指向Class的prototype,而block的isa指向了_NSConcreteStackBlock這個指標。就是說當一個block被宣告的時候他都是一個_NSConcreteStackBlock類的物件。
flags和reserved這兩個基本就是某些標記(暫時沒那麼重要)
FuncPtr這個就是函式指標,也就是block所需要執行的程式碼段,真正存的地址
3.第二個成員變數Desc
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
顧名思義reserve和Block_size分別代表了版本升級所需的區域和Block的大小4.第三個就是這個結構體的建構函式(可以理解為物件的初始化方法)
__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;
}
可以通過這裡看出,初始化的時候會右fp指標(這裡就是函式指標),以及desc結構體的指標傳進來初始化,void *fp的指標賦值給了FuncPtr指標,以上的結構和初始化函式就是基本的Block生成的原始碼原始碼呼叫部分分析
5.現在來看看Main函式中呼叫的基本轉換(初始化轉換)
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
看起來這個函式好嚇人啊,沒事,咱們把型別都去了,而且分開兩步來寫
/* 呼叫結構體函式初始化
struct __main_block_impl_0 impBlock = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
/* 賦值給該結構體型別指標變數
struct __main_block_impl_0 *block = &impBlock;
這非常容易理解了吧,就是把棧上生成的__main_block_impl_0的結構體例項的指標,賦值給__main_block_impl_0結構體指標型別的變數block
__main_block_func_0就是轉換成的函式指標,這樣void (*block)(void) = ^{}這句簡單的程式碼最終就是上面的結構體初始化函式的內部實現邏輯
6.再來細看下初始化函式內部的實現__block_main_impl_0
__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
第一個引數是由Block塊語法轉換的正真內部函式指標,第二個引數就是作為靜態全域性變數初始化__main_block_desc_0的結構體例項指標,初始化如下__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
最終,初始化內部進行賦值
isa = &_NSConcreteStackBlock
Flags = 0
Reversed = 0
FuncPtr = __main_blcok_func_0(就是Block塊程式碼轉換成的C語言函式指標)
Desc = &__ main_block_desc_0_DATA
7.最終block()呼叫的內部實現
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
老規矩 ,去掉型別就是(*block->imp.FuncPtr)(block);
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("aaa\n");}
沒錯,最後就是直接呼叫函式指標進行最終的呼叫,由上面所描述的,FuncPtr就是由__main_block_func_0的函式指標所賦值的指標,而且可以看出,這個函式的引數正是指向block本身結構體例項,block作為引數進行了傳遞擴充下:
還記得初始化的時候內部出現這句話了麼
isa = &_NSConcreteStackBlock
看過runtime原始碼的應該能明白,objc_object結構體有個Class isa,這個isa指向下面這個結構體
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
constchar *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
}
簡單來說這個isa就是指向其類或者類的父類的指標,包含了父類各種方法和屬性,感覺說不明白啊,我擦這麼理解吧,isa指標__NSConcreteStackBlock相當於class的結構體例項,關於該類的所有資訊都包含在這個地址裡面,跑起來的時候就可以通過該指標去父類找需要的東西了,那麼Block最簡單的實質應該有個初步瞭解了吧,明白了Block其實就是OC裡面的物件了
概括下:
1.__main_block_impl_0這個就是Block內部的結構體(該結構體成員有impl,Desc 和一個該結構體的初始化函式)
2.Block塊內的程式碼轉換成了__main_block_func_0的c語言函式(引數就是__main_block_impl_0結構體)
3.Block語法的初始化實際就是將__main_blcok_impl_0的結構體例項化,重點是Block的程式碼塊{}通過轉換成C的函式指標進行結構體的成員變數FuncPtr指標賦值
4.Block呼叫就是通過例項化的結構體裡面的FuncPtr指標就行函式呼叫,而且引數就是該結構體本身(暫時沒用到這個傳出去的結構體,另外寫一個介紹帶引數是如何完成回撥的,如何截獲變數等等)
收工~~~~~~~~睡覺~~~~~~~~~有不同理解告訴我,看我不打死你~~~~~~
看第二集的同學可以點選點選看第二集,繼續聽我叨逼叨,反正瞎BB的,對了,別看C的程式碼很長,其實分析下很簡單的呦......