Blocks 概要
Blocks是C語言的擴(kuò)充功能??梢杂靡痪湓拋肀硎綛locks的擴(kuò)充功能:帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
“帶有自動(dòng)變量值”究竟是什么呢。
先看看C函數(shù)中可能使用的變量。
- 自動(dòng)變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
其中,在函數(shù)中多次調(diào)用之間能夠傳遞值的變量有: - 靜態(tài)變量 (靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
雖然這些變量的作用域不同,但在整個(gè)程序當(dāng)中,一個(gè)變量總保持在一個(gè)內(nèi)存區(qū)域。因此,雖然多次調(diào)用函數(shù),但該變量值總能保持不變,在任何時(shí)候以任何狀態(tài)調(diào)用,使用的都是同樣的變量值。
Blocks類型的變量可完全像通常的C語言變量一樣使用
Blocks底層結(jié)構(gòu)
首先我們通過clang將含有Blcoks語法的源代碼轉(zhuǎn)換為C++的源代碼,具體如下
1.新建.m文件

OC代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
}
return 0;
}
2.打開終端 cd 到main.m所在文件夾
3.輸入clang -rewrite-objc main.m,就會(huì)在當(dāng)前文件夾內(nèi)自動(dòng)生成對(duì)應(yīng)的main.cpp文件
文件非常長(zhǎng),我們直接拉到最后,找到main函數(shù)
C++代碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
首先來看最初的源代碼中的Block語法
^{
printf("Block\n");
};
可以看到,變換后的源代碼也含有相同的表達(dá)式
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
該函數(shù)的參數(shù)*__cself 為指向Block的值的變量(相當(dāng)于C++里的this,OC里的self)
__cself是__main_block_impl_0 的指針
先看一下__main_block_impl_0 的結(jié)構(gòu)體,該結(jié)構(gòu)體我們可以從command+f 在main.cpp文件中搜索得到:
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;
}
};
該結(jié)構(gòu)體的聲明如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
}
由于轉(zhuǎn)化源碼之后一并寫入了構(gòu)造函數(shù),所以看起來稍顯復(fù)雜,第一個(gè)成員變量是impl,我們先來看一下其 __block_impl impl結(jié)構(gòu)體的聲明
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
第二個(gè)成員變量是Desc指針,以下為__main_block_desc_0結(jié)構(gòu)體的聲明
static struct __main_block_desc_0 {
size_t reserved;//今后版本升級(jí)所需的區(qū)域
size_t Block_size;//Blcok的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
下面來看一初始化含有這些結(jié)構(gòu)體__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)
__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結(jié)構(gòu)體的源代碼。
總結(jié)如圖

接下來先看一下該構(gòu)造函數(shù)的調(diào)用
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉轉(zhuǎn)化部分為
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
該源代碼將struct __main_block_impl_0 結(jié)構(gòu)體的自動(dòng)變量,既棧上生成的__main_block_impl_0 結(jié)構(gòu)體實(shí)例的指針賦值給__main_block_impl_0 結(jié)構(gòu)體指針類型的變量blk。
以下為這部分對(duì)應(yīng)的最初源代碼
void (^blk)(void) = ^{
printf("Block\n");
};
將Block語法生成的Block賦給Block類型變量blk,它等同于將__main_block_impl_0結(jié)構(gòu)體實(shí)例指針賦值給變量blk。該源代碼中的Block就是__main_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__main_block_impl_0 結(jié)構(gòu)體實(shí)例
下面看下__main_block_impl_0結(jié)構(gòu)體實(shí)例構(gòu)造函數(shù)。
__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
第一個(gè)參數(shù)是Block語法轉(zhuǎn)化的C函數(shù)指針。第二個(gè)參數(shù)是靜態(tài)全局變量初始化的__main_block_desc_0結(jié)構(gòu)體實(shí)例指針。以下為__main_block_desc_0結(jié)構(gòu)體實(shí)例的初始化部分代碼
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
由此可知,該源碼使用Block,即__main_block_impl_0結(jié)構(gòu)體實(shí)例的大小,進(jìn)行初始化。
__main_block_func_0 參數(shù)是有Blcoks語法轉(zhuǎn)換的C語言函數(shù)指針。
&__main_block_desc_0_DATA是作為__main_block_desc_0結(jié)構(gòu)體的實(shí)例指針。
我們來確認(rèn)下使用該Block的部分
blk();
這部分可轉(zhuǎn)化為以下源代碼
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉轉(zhuǎn)化部分
(*blk->impl.FuncPtr)(blk)
正如我們剛剛確認(rèn)的,有Block語法轉(zhuǎn)換的__main_block_func_0函數(shù)指針被賦值成員變量FuncPtr中。另外也說明了FuncPtr函數(shù)的參數(shù)
__cself指向了Block的值。在調(diào)用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進(jìn)行了傳遞。
Blcok即為OC的對(duì)象
截獲自動(dòng)變量值
Block解釋為帶有自動(dòng)變量(局部變量)的匿名函數(shù),帶有“自動(dòng)變量值”在Blocks中表現(xiàn)為“截獲自動(dòng)變量的值”。截獲自動(dòng)變量的值的實(shí)例如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d \n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;
fmt = "these values were change. val = %d \n";
blk();
}
return 0;
}
執(zhí)行結(jié)果
val = 10
該源代碼中Block語法的表達(dá)式使用的是它之前聲明的自動(dòng)變量fmt和val。Blocks中,Block表達(dá)式獲取所使用的自動(dòng)變量的值,即保存該自動(dòng)變量的瞬間值。所以在執(zhí)行Block語法后即使改寫B(tài)lcok中使用的自動(dòng)變量值也不會(huì)影響B(tài)lcok執(zhí)行時(shí)自動(dòng)變量的值。
這就是自動(dòng)變量值的截獲。
通過clang -rewrite-objc main.m 分析下源代碼
C++代碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int dmy = 256;
int val = 10;
const char *fmt = "val = %d \n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "these values were change. val = %d \n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
__main_block_impl_0結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我們注意到,Blcok語法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到了 __main_block_impl_0結(jié)構(gòu)體中,請(qǐng)注意沒有使用的自動(dòng)變量不會(huì)增加如dmy。
在轉(zhuǎn)換后的源代碼中,截獲到 _main_block_impl_0結(jié)構(gòu)體實(shí)例的成員變量上的自動(dòng)變量,這些變量在Block語法表達(dá)式之前被定義。
因此 所謂“截獲自動(dòng)變量值”意味著在執(zhí)行Block語法時(shí),Block語法表達(dá)式所使用的自動(dòng)變量值被保存到了Block的結(jié)構(gòu)體實(shí)例(即Blcok自身)中
若想在Blcok語法的表達(dá)式中將值付給在Block語法外的自動(dòng)變量,需要在該自動(dòng)變量上附加 _block說明符。
