Block技術(shù)合集
閱讀本文前祝蝠,請(qǐng)先思考如下問(wèn)題
- 為什么Block可以截獲變量
- 為什么Block外定義的基本數(shù)據(jù)類(lèi)型犀暑,在Block內(nèi)部不能修改
- 為什么用__block修飾后擎鸠,在Block內(nèi)部可以修改
本文將對(duì)Block底層探索并解答如上三個(gè)問(wèn)題
什么是Block
帶有自動(dòng)變量值的匿名函數(shù)
Block截獲變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^blk)(void) = ^{
printf("Block\n");
};
blk();
}
return 0;
}
編譯成成cpp
代碼, 代碼非常多细办,我們精簡(jiǎn)如下
//1. 結(jié)構(gòu)體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//2.
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;
}
};
//3.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
//4.
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)};
//5. main函數(shù)代碼塊
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;
}
總共4個(gè)結(jié)構(gòu)體和一個(gè)main函數(shù)代碼塊
查看5. main函數(shù)代碼塊
可見(jiàn)哆档,block對(duì)象被編譯成了__main_block_impl_0
類(lèi)型的結(jié)構(gòu)體, 這個(gè)結(jié)構(gòu)體由兩個(gè)成員結(jié)構(gòu)體和一個(gè)構(gòu)造函數(shù)組成燃辖,兩個(gè)結(jié)構(gòu)體分別是__block_impl
和__main_block_desc_0
類(lèi)型的锯玛,其中__block_impl
結(jié)構(gòu)體中有一個(gè)函數(shù)指針接奈, 指針指向__main_block_func_0
類(lèi)型的結(jié)構(gòu)體踢涌,總結(jié)關(guān)系圖如下:
Block在定義的時(shí)候:
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
Block在調(diào)用的時(shí)候:
((__block_impl *)blk)->FuncPtr
Block內(nèi)部的函數(shù)打印,很顯然放在了__main_block_func_0
序宦,那么block內(nèi)部截獲的數(shù)據(jù)存放在哪呢睁壁?同樣 我們對(duì)如下代碼進(jìn)行編譯
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void(^blk)(void) = ^{
printf(" Block\n a = %d\n", a);
};
blk();
}
return 0;
}
編譯成cpp
//1.
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;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf(" Block\n a = %d\n", a);
}
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;
int a = 10;
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
很顯然的是,__main_block_impl_0
結(jié)構(gòu)體增加了成員變量int a;
并且在結(jié)構(gòu)體的構(gòu)造函數(shù)__main_block_func_0
中對(duì)變量進(jìn)行賦值int a = __cself->a
,而這一賦值操作,在Block定義的時(shí)候就已完成(并非在Block調(diào)用的時(shí)候)互捌,這也是Block截獲變量的原理(文章開(kāi)頭問(wèn)題1:為什么Block可以截獲變量)潘明。Block對(duì)不同數(shù)據(jù)類(lèi)型截獲方式請(qǐng)查看我之前寫(xiě)的iOS - Block變量截獲
為什么Block中不能修改變量值
我們先把代碼做微小的修改,即對(duì) block外定義的變量'int a = 10'秕噪, 分別在block定義前后及block內(nèi)部打印其地址
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
printf("before block &a = %p \n", &a);
void(^blk)(void) = ^{
printf(" Block\n a = %d\n in block &a = %p \n ", a, &a);
};
printf("after block &a = %p \n\n", &a);
blk();
}
return 0;
}
打印如下:
before block &a = 0x7ffeefbff4ec
after block &a = 0x7ffeefbff4ec
Block
a = 10
in block &a = 0x1004385f0
很明顯的是钳降,外block外部打印的int a
地址一致,但在block內(nèi)部卻不一樣了,即block內(nèi)部的a
并不是我們外部定義的int a
(此時(shí)作者想起了一首歌:你說(shuō)的黑不是黑腌巾,你說(shuō)的白是神魔TM的白...)
這里問(wèn)題二的答案已經(jīng)很明顯了遂填,為什么block內(nèi)部無(wú)法修改外部的變量,因?yàn)榫筒皇峭粋€(gè)變量啊澈蝙,只是長(zhǎng)的一樣而已
有人就問(wèn)了吓坚,那block內(nèi)部的那個(gè)a究竟是誰(shuí)從哪里來(lái)?請(qǐng)看前邊編譯的cpp
代碼中block方法的結(jié)構(gòu)體__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf(" Block\n a = %d\n", a);
}
此時(shí)你應(yīng)該恍然大悟灯荧,這個(gè)a是block內(nèi)部重新定義的a
礁击,取值自block外部定義的int a = 10
,至此,block內(nèi)部無(wú)法修改外部變量的問(wèn)題顯而易見(jiàn):
為什么無(wú)法修改:因?yàn)椴皇峭粋€(gè)值哆窿,地址不一樣
內(nèi)部的a
變量哪來(lái)的:block底層重新定義的链烈,取值自外部(相當(dāng)于副本)
為什么用__block修飾后,在Block內(nèi)部可以修改
先附上__block修飾前編譯的main函數(shù)源碼(用于下文做比較)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
printf("before block &a = %p \n", &a);
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
printf("after block &a = %p \n\n", &a);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
不廢話挚躯,改代碼加__block修飾测垛,先打印看看
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
printf("before block &a = %p \n", &a);
void(^blk)(void) = ^{
printf(" Block\n a = %d\n in block &a = %p \n ", a, &a);
};
printf("after block &a = %p \n\n", &a);
blk();
}
return 0;
}
before block &a = 0x7ffeefbff4e8
after block &a = 0x103009f98
Block
a = 10
in block &a = 0x103009f98
根據(jù)打印,很明顯的能看到秧均,a
在__block修飾定義時(shí)的地址食侮,與block內(nèi)部及block定義后的地址不一致,此處大膽猜測(cè)目胡,__block修飾的變量锯七,在block定義時(shí),會(huì)生成新的對(duì)象(下文得知是結(jié)構(gòu)體)誉己,在block外部獲取眉尸、更改該變量時(shí),獲取的是這個(gè)新生成的對(duì)象
我們編譯一下
//1.
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
//2.
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//3.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf(" Block\n a = %d\n in block &a = %p \n ", (a->__forwarding->a), &(a->__forwarding->a));
}
//4.
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
printf("before block &a = %p \n", &(a.__forwarding->a));
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
printf("after block &a = %p \n\n", &(a.__forwarding->a));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
編譯后源碼先找不同
- 定義
int a = 10
變成了__Block_byref_a_0 a = 10(精簡(jiǎn))
- 多了各結(jié)構(gòu)體
__Block_byref_a_0
,而此結(jié)構(gòu)體內(nèi)部有int a
-
__main_block_impl_0
結(jié)構(gòu)體中的int a
不見(jiàn)了巨双,多了個(gè)__Block_byref_a_0 *a
-
__main_block_func_0
結(jié)構(gòu)體中的int a = __cself->a
變成了__Block_byref_a_0 *a = __cself->a
-
block
外部的printf("after block &a = %p \n\n", &a)
變成了printf("after block &a = %p \n\n", &(a.__forwarding->a))
上文不同翻譯總結(jié)一下就是答案:
變量添加__block修飾后噪猾,變量會(huì)被封裝稱(chēng)結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部包含變量筑累,
在block內(nèi)部修改變量時(shí)袱蜡,修改的是結(jié)構(gòu)體__Block_byref_a_0
內(nèi)部的變量數(shù)據(jù)(a->__forwarding->a)
(所以可以修改)
出了block作用域后,修改數(shù)據(jù)修改的仍然是__Block_byref_a_0
內(nèi)部的變量數(shù)據(jù)(a.__forwarding->a)
疑問(wèn):
printf("before block &a = %p \n", &(a.__forwarding->a));
printf("after block &a = %p \n\n", &(a.__forwarding->a));
before block &a = 0x7ffeefbff4e8
after block &a = 0x100474798 `
查看編譯后底層代碼慢宗,打印地址查找都是&(a.__forwarding->a))
坪蚁,為什么打印出來(lái)的地址不同