block
1.block的實質
之前說過其實block的本質就是"帶有自動變量的匿名函數"。block類型的變量與函數指針類似,僅是將 * 號換成了 ^ 符號.那么block的底層實現又是如何。 這里可以通過 clang命令將block語法轉換成底層的c語言。具體的實現: 我們可以打開終端,進入想要轉換的block文件目錄下,執行"clang -rewrite-objc 文件名"指令。至此就能將原文件轉換成.cpp的底層文件了。
void test() {
void(^blk)() = ^(){
printf("is block");
};
blk();
}
以上的代碼通過clang 指令后,轉換成以下的源碼。(這里指摘取出與block語法相關的代碼)
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
printf("is block");
}
void test() {
void(*blk)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
首先,看到原來的test 函數轉換成了
//源代碼
void test() {
//將block函數賦值給變量blk
void(^blk)() = ^(){
printf("is block");
};
//執行block
blk();
}
<===>
//轉換后的代碼
void test() {
//將block函數賦值給變量blk
void(*blk)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA));
//執行block
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
<===>
//為了更好的濾清代碼,去掉類型的強轉
//去除強轉后的代碼
void test() {
//將block函數賦值給變量blk
*blk = & __test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA);
//執行block
blk->FuncPtr(blk);
}
先看 將block函數賦值給變量blk 這段代碼,它的實質是將 " _ test_block_impl_0類型的實例的指針”賦值給了 “ test_block_impl_0指針類型的變量blk”。 而 _ _test_block_impl_0 是一個結構體。
__test_block_impl_0結構體
為什么這個結構體的名稱叫 __test_block_impl_0,它的命名規則是 “ _ _ _block所在的函數的名字block_impl第幾個block ”。
void test1() {
void(^blk)() = ^(){
printf("is block");
};
blk();
void(^blk2)() = ^(){
printf("is block2");
};
blk2();
}
則結構體為
__test1_block_impl_0
__test1_block_impl_1
接下來看看 __test_block_impl_0結構體具體的構造
struct __test_block_impl_0 {
struct __block_impl impl; //成員變量 impl
struct __test_block_desc_0* Desc; // 成員變量 Desc
//結構體的構造方法
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
它由2個成員變量和一個構造函數組成. 第一個成員變量impl是一個 __block_impl類型的結構體
struct __block_impl {
void *isa; // isa 指針
int Flags; //標記 Flags
int Reserved;
void *FuncPtr; //函數指針 FuncPtr
};
第二個成員變量desc 是一個指向 " _ _test_block_desc_0" 結構體的指針。并且定義了一個名為__test_block_desc_0_DATA 的變量(初始化block時用到)。
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size; //block大小
}
__test_block_desc_0_DATA = {
0,
sizeof(struct __test_block_impl_0) // __test_block_impl_0大小
};
再來看下源代碼中初始化結構體的函數,將參數帶入到結構體構造函數后
& __test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA);
<===>
__test_block_impl_0(*__test_block_func_0, &__test_block_desc_0_DATA, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = __test_block_func_0;
Desc = __test_block_desc_0_DATA;
}
這里還需要解釋一個 _test_block_func_0 的變量,它的本質是一個普通的函數,命名規則 " _ _block所在的函數名block_func第幾個block"。其實它就是原block的執行部分。
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
printf("is block");
}
<===>
相當于原block函數的執行部分
^(){
printf("is block");
};
參數 _ _cself 是block本事,相當于 oc 函數中的self。(在改block中還沒有用到__cself)
block的調用
最后就是執行block,通過上面的分析我們其實就知道了block的調用就是通過函數指針執行block函數。并將block本事作為參數傳入。
blk->FuncPtr(blk);
<===>
blk.impl.FuncPtr = tempFuncptr;
*tempFuncptr = &__test_block_func_0;
__test_block_func_0(blk);
2.引用外部變量的實質
當block引用了外部變量的時候,它與沒有引用外部變量的時候,通過clang轉換的源碼來看是有不同的。例如,下面的這段引用了外部變量的block
void testBlock2() {
int a = 10;
int b = 20;
void(^blk)() = ^(){
printf("a = %d",a);
};
blk();
}
通過 clang指令,它將轉換成了如下的代碼
<=== 以下代碼與未使用外部變量有區別 ===>
void testBlock2()
int a = 10;
int b = 20;
void(*blk)() = ((void (*)())&__testBlock2_block_impl_0((void *)__testBlock2_block_func_0, &__testBlock2_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
struct __testBlock2_block_impl_0 {
struct __block_impl impl;
struct __testBlock2_block_desc_0* Desc;
int a;
__testBlock2_block_impl_0(void *fp, struct __testBlock2_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __testBlock2_block_func_0(struct __testBlock2_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("a = %d",a);
}
<=== 以下代碼與未使用外部變量相同 ===>
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __testBlock2_block_desc_0 {
size_t reserved;
size_t Block_size;
} __testBlock2_block_desc_0_DATA = { 0, sizeof(struct __testBlock2_block_impl_0)};
來對比一下與未使用外部變量時區別的部分,首先是__testBlock2_block_impl_0這個結構體,它多出了一個與引用的外部變量相同類型的成員變量。(這里只添加了block內部使用了的外部變量a這一個成員變量,外部變量b沒有使用,所以沒有添加到結構體中):
struct __testBlock2_block_impl_0 {
struct __block_impl impl;
struct __testBlock2_block_desc_0* Desc;
int a; //引用的外部變量 追加成了 __testBlock2_block_impl_0 結構體的成員變量
};
再來看一下__testBlock2_block_impl_0實例初始化的方法:
__testBlock2_block_impl_0(void *fp, struct __testBlock2_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
這個初始化方法與未使用外部變量時的區別是多了一個參數a,而: a(_a)這個語法是c++中結構體中給const類型的變量初始化的意思。即將參數_a賦值給結構體的變量a。