簡述
一句話搞懂block:可以理解為,block是對上下文代碼段的打包,然后在適當的時機執行。
block長什么樣
block語法和C語言的函數指針語法非常相似,比如函數指針的聲明:
// C函數指針聲明
// 返回值類型為int,有兩個int型參數的函數指針
int (*ptr)(int, int);
typedef (*Ptr)(int, int);
而block把 * 換成了 ^ :
// block聲明
int (^func)(int, int);
typedef (^Func)(int, int);
編譯器對block的處理
要深入block的底層原理,首先要知道編譯器對block進行了什么樣的處理,接下來一起看看。
1、在block.c文件中,簡單編寫以下代碼:
#include <stdio.h>
int main () {
// 變量
int num = 0;
const char *str = "Hello World!";
// 定義block
void (^foo)(void) = ^{
int var1 = num + 1;
char var2 = str[0];
};
// 調用block
foo();
return 0;
}
2、把代碼重寫成c++,命令行使用:clang -rewrite-objc block.c
,得到:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
const char *str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, const char *_str, int flags=0) : num(_num), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
const char *str = __cself->str; // bound by copy
int var1 = num + 1;
char var2 = str[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 num = 0;
const char *str = "Hello World!";
void (*foo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num, str));
((void (*)(__block_impl *))((__block_impl *)foo)->FuncPtr)((__block_impl *)foo);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
分析block實現
1、首先,我們定義了變量:
int num = 0;
const char *str = "Hello World!";
2、然后第二步,定義block:
void (^foo)(void) = ^{
int var1 = num + 1;
char var2 = str[0];
};
它被重寫成了:
void (*foo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num, str));
這里的__main_block_impl_0
是什么呢?它是一個結構體,結構如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
const char *str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, const char *_str, int flags=0) : num(_num), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
從block的聲明,重寫之后代碼中可以看到,它被處理成:調用__main_block_impl_0
這個結構體構造方法,會把外層變量num
、str
傳進來,賦值給結構體的成員變量。
OK。到這里,可以引伸出以下結論:
1) 當我們寫一個block,實際上是寫一個結構體
2) block的捕獲特性。捕獲變量,實際上是通過賦值給結構體的成員變量
3、接著第三步,調用block:
((void (*)(__block_impl *))((__block_impl *)foo)->FuncPtr)((__block_impl *)foo);
這里可以得知,block的調用,實際上是調用foo
這個結構體里面的FuncPtr
這個函數。
FuncPtr
是在構造函數傳遞進來的,傳進來的是__main_block_func_0
這個函數地址。那么這個函數是什么呢?
在重寫后的代碼可以看到:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
const char *str = __cself->str; // bound by copy
int var1 = num + 1;
char var2 = str[0];
}
其實到這里,我們已經可以得到以下結論:
當我們寫一個block,實際上是寫 一個結構體 + 一個靜態函數。
結構體 - 負責捕獲變量,
靜態函數 - 在block被調用時,會調用這個靜態函數。
補充
我們都知道,正常在block內是不能修改外層變量的(不加__block),比如:
int num = 0;
void (^foo)(void) = ^{
num = num + 1;
};
現在知道為什么了吧,因為以上代碼相當于修改了捕獲進來的變量,影響不了外層:
int num = 0;
void (^foo)(void) = ^{
int num_in_block = num; // 捕獲進來
num_in_block = num_in_block + 1; // 修改的是捕獲進來的變量num_in_block,而不是外層的num
};
如果加了__block之后,到底會起什么作用呢? 嘗試著動手試試吧 ~ 提前透露下,比如 __block int x,int 會被重寫成結構體 struct_has_int_x 喲,interesting ~