一、準(zhǔn)備工作
- 1、創(chuàng)建一個
命令行項目 - 2、Mac自帶的終端
Terminal
進入創(chuàng)建好的項目,并在
mian.m里面定義一個Block, 如下所示:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//定義一個block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!");
};
//block調(diào)用
myBlock();
}
return 0;
}
打開
Terminal,cd到當(dāng)前項目main.m所在目錄,執(zhí)行以下指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
上面的指令目的是借助clang編譯main.m得到編譯后的文件-main.cpp。
我把編譯后的主要的代碼貼出來,以便進行后面的探究。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
#pragma clang assume_nonnull end
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}
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;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
二、定義Block探究
由于我們是在main函數(shù)里面定義的Block,所以我們在編譯后的文件里也是對應(yīng)從main函數(shù)開始探究。
如果我們把代碼對應(yīng)起來的話就是下面這樣的:
- 定義block,在編譯前和編譯后
編譯前:
//定義一個block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!");
};
編譯后:
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
編譯后去除一些強制轉(zhuǎn)換操作后:
void (*myBlock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
經(jīng)過上面的簡化我們不難發(fā)現(xiàn):
- 調(diào)用了
__main_block_impl_0 (參數(shù)1, 參數(shù)2),并把返回值的地址(&)賦值給了myBlock。- 2、參數(shù)1是:
__main_block_func_0- 3、參數(shù)2是:
&__main_block_desc_0_DATA),把參數(shù)2的地址值(&)傳遞進去了.
1、探究__main_block_impl_0
所以下一步,我們需要去看看__main_block_impl_0函數(shù)是什么?
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//構(gòu)造函數(shù)(類似OC的init方法,把外面?zhèn)鬟M來的參數(shù)賦值給自己的成員變量,并返回self),返回結(jié)構(gòu)體對象
__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 的 c++結(jié)構(gòu)體,里面有:
- 一個
構(gòu)造函數(shù) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)- 兩個主要的成員變量
struct __block_impl impl和struct __main_block_desc_0* Desc。
所以,我們不難得出,
Block其實是一個結(jié)構(gòu)體對象。
從外面?zhèn)鬟M來的參數(shù)賦值給了它的兩個成員變量,所以下一步我們需要弄清楚,這兩個成員變量是什么。
2、探究__block_impl
在編譯后的文件中我們可以找到:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl結(jié)構(gòu)體里面包含的:
void *isa, 結(jié)構(gòu)體的地址void *FuncPtr, 函數(shù)的地址int Flags,結(jié)構(gòu)體的標(biāo)識int Reserved,是一個保留字段
看到這里,我們就不難發(fā)現(xiàn)這個
Block的內(nèi)存地址其實就是 __block_impl 的 isa所指向的地址。
為什么這么說?看下面我們貼出的__main_block_impl_0的結(jié)構(gòu)體,這里就不再細說了
struct __main_block_impl_0 {
struct __block_impl impl;
...
}
3、探究__main_block_desc_0
在編譯后的文件中我們可以找到:
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)};
size_t reserved,保留字段默認值是0
size_t Block_size,存儲了Block的內(nèi)存大小,通過sizeof(struct __main_block_impl_0)計算出來的
也就是說,這個結(jié)構(gòu)體主要是來存儲Block的描述信息,如:內(nèi)存大小等。
4、看下_main_block_impl_0(...)構(gòu)造函數(shù)
現(xiàn)在清楚了Block的兩個成員變量后,我們就來看看它的構(gòu)造函數(shù)接收的參數(shù)。
_main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
int flags=0
)
它接收3個參數(shù):
- 1.
void *fp,在函數(shù)里面把它賦值給了impl.FuncPtr。 - 2.
struct __main_block_desc_0 *desc,在函數(shù)里面把它賦值給了Desc. - 1.
int flags=0,這個是有一個默認值,在上面的調(diào)用過程中沒有傳第三個參數(shù),說明 當(dāng)前情況下使用默認值就可以。
再來看下編譯后這個構(gòu)造函數(shù)接收的具體參數(shù):
void (*myBlock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
5、探究__main_block_func_0
__main_block_func_0是block接收的第一個參數(shù),我們可以在編譯后的文件中找到
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}
__main_block_func_0就是Block封裝了執(zhí)行邏輯的函數(shù),現(xiàn)在我們所看到的內(nèi)部封裝的要執(zhí)行的函數(shù)就是一開始寫在Block里面的輸出函數(shù):NSLog(@"Hello Block!");
所以第一個參數(shù):
把封裝了要執(zhí)行函數(shù)的函數(shù)地址傳給了__main_block_impl_0, 里面把函數(shù)地址賦值給了 impl.FuncPtr = fp;
6、探究__main_block_desc_0_DATA
__main_block_desc_0_DATA是block接收的第二個參數(shù),我們可以在編譯后的文件中找到:
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)};
從上面可以看出:
-
__main_block_desc_0_DATA的0傳給了__main_block_desc_0的size_t reserved. -
__main_block_desc_0_DATA的sizeof(struct __main_block_impl_0)傳給了__main_block_desc_0的size_t Block_size.
所以第二個參數(shù), 實際上是計算了這個
Block的內(nèi)存大小,并把得到的這個結(jié)構(gòu)體的地址值傳遞進去。
到此,結(jié)束了定義一個Block的本質(zhì)的探究。
三、Block調(diào)用的
首先,我們回看一下調(diào)用的代碼:
//block調(diào)用
myBlock();
編譯后
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
編譯后去除強制轉(zhuǎn)換就變成如下的代碼
myBlock->FuncPtr(myBlock);
上面的分析我們知道:
myBlock是__main_block_impl_0構(gòu)造函數(shù)創(chuàng)建完后返回的指針地址myBlock->FuncPtr這句話的作用:
通過myBlock的地址拿到impl,再通過impl拿到里面的FuncPtr保存的地址值,然后再調(diào)用方法- 傳進去
(myBlock)的地址就是傳給了封裝要執(zhí)行函數(shù)的函數(shù), 即 static void __main_block_func_0(struct __main_block_impl_0 *__cself)
所以到此也就完成了本次對Block本質(zhì)的探究!
四、總結(jié)一下
1、
Block本質(zhì)上也是一個OC對象,內(nèi)部也有一個isa指針。2、
Block是封裝了函數(shù)調(diào)用和函數(shù)調(diào)用環(huán)境的OC對象-
3、
Block內(nèi)部的兩個主要成員:struct __block_impl impl,保存了Block的內(nèi)存地址,封裝要執(zhí)行函數(shù)的函數(shù)地址等
struct __main_block_desc_0* Desc,內(nèi)部主要保存了Block的內(nèi)存大小。