block的使用
block是什么
** block就是可以截獲局部變量的匿名函數(shù)壕翩。**
解釋一下:** block可以獲取被定義時(shí)詞法范圍內(nèi)的狀態(tài)(比如局部變量等)祸轮,并且在一定條件下(比如使用__block變量)可以修改這些狀態(tài)迷守。 **
比如蒜魄,在某方法中的一個(gè)block暖呕,是可以獲取到該方法內(nèi)的變量的斜做。
block的語法
比如下面定義了一個(gè):名為addBlock,參數(shù)列表是兩個(gè)int型的數(shù)據(jù)缰揪,返回值也為int型的block陨享。
int (^addBlock)(int, int) = ^(int x, int y){
return x + y;
};
int result = addBlock(2,4); // 傳入實(shí)參葱淳,執(zhí)行該block,返回了int型的結(jié)果抛姑。
block和其他變量一樣都可以局部變量赞厕,全局變量,靜態(tài)變量定硝,甚至方法參數(shù)等皿桑。
block既然是種變量,那它也就有自己所屬的類型蔬啡。決定一個(gè)block是什么類型的因素是返回值和參數(shù)诲侮。int (^addBlock)(int, int)
代表返回值為int型,兩個(gè)int型參數(shù)箱蟆,名為addBlock沟绪。但這樣表示block有點(diǎn)不太好。其一是閱讀起來不太順暢空猜,其二是若我們要重構(gòu)或者修改原來定義的block绽慈,則要在每個(gè)使用該block的地方進(jìn)行手工修改。所以我們可以統(tǒng)一在一個(gè)地方對(duì)其進(jìn)行類型再定義辈毯。
// 把返回值為void型坝疼,倆int型參數(shù)的block統(tǒng)一再定義為MyBlock類型。
typedef void (^MyBlock)(int);
...
MyBlock myBlock = ^(int x){
NSLog(@"myBlock:rereult = %d", x);
};
// 或者block作為方法參數(shù)時(shí)
- (void)doSomething:(MyBlock)myBlock param:(int)count
{
// 調(diào)用myBlock
myBlock(count);
}
注意:block的語法本身就比較怪異谆沃,再加上:定義block時(shí)(^blockName)
括號(hào)里面的是block名字钝凶,但是通過typedef
進(jìn)行類型再定義時(shí)(^blockClass)
括號(hào)里表示代表該block的類型名⊙溆埃總之耕陷,block的語法比較別扭,別記錯(cuò)了夭咬。
截獲局部變量
開頭我們說了block是可以截獲局部變量的匿名函數(shù)
啃炸。也就是說在某方法內(nèi)的block是可以獲取該方法定義的局部變量的。** 而且是只讀的卓舵,不可以對(duì)其進(jìn)行修改操作南用。若非要進(jìn)行修改,則得在局部變量前加上__block
修飾符掏湾。**下面用三小段代碼分別來驗(yàn)證:
// block內(nèi)可以讀取局部變量
int count = 10;
void (^countBlock1)(void) = ^(void){
NSLog(@"count----%d",count);
};
countBlock1();
// BlockWang[1534:689473] count----10
// 試圖在block內(nèi)修改局部變量
int count = 10;
void (^countBlock1)(void) = ^(void){
count++;
};
countBlock1();
上面這段代碼編譯時(shí)會(huì)報(bào)錯(cuò):
// 在局部變量前加上__block修飾符裹虫,后就可以在block內(nèi)部修改此局部變量了
__block int count = 10;
void (^countBlock1)(void) = ^(void){
NSLog(@"count----%d",++count);
};
countBlock1();
// BlockWang[1534:689473] count----11
需要小心下面這段代碼:我們?cè)诙x一個(gè)block后再修改了count值為2,然后再執(zhí)行該block融击。執(zhí)行的打印結(jié)果是count----10
筑公,這就說明block“截獲局部變量”的處理是在定義這個(gè)block時(shí),而且似乎所謂“截獲局部變量”就是在block中有了個(gè)和count相應(yīng)的獨(dú)立的數(shù)據(jù)尊浪,不然我們當(dāng)修改count值時(shí)匣屡,為什么打印出的block內(nèi)的該變量沒變化呢封救?這個(gè)疑問在后面block的實(shí)現(xiàn)中我們慢慢分析。
int count = 10;
void (^countBlock)(void) = ^(void){
NSLog(@"count----%d",count);
};
count = 2;
countBlock(); // 執(zhí)行block
// BlockWang[1534:689473] count----10
block的實(shí)質(zhì)
接下來我們會(huì)把代碼通過Clang命令轉(zhuǎn)換為中間代碼來觀察block的實(shí)現(xiàn)捣作,探索它的本質(zhì)誉结。
block的實(shí)現(xiàn)結(jié)構(gòu):
首先我們研究只打印字符串的,最簡(jiǎn)單的block:
#include "BlockClang.h"
int main()
{
void (^myBlock)(void) = ^(void){
printf("this is a block");
};
myBlock();
return 0;
}
打開終端券躁,進(jìn)入項(xiàng)目路徑惩坑,然后敲入Clang的命令clang -rewrite-objc BlockClang.c
。此時(shí)也拜,F(xiàn)inder里多了個(gè)文件BlockClang.cpp
以舒,它正是轉(zhuǎn)換后的中間代碼。
小小的一段代碼轉(zhuǎn)換為BlockClang.cpp后竟然有超500多行慢哈,我們只提取出對(duì)我們有意義的部分:
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;
__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) {
printf("this is a block");
}
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()
{
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的結(jié)構(gòu)實(shí)現(xiàn)是結(jié)構(gòu)體,其中__main_block_impl_0
結(jié)構(gòu)體代表block的結(jié)構(gòu)卵贱。它有一個(gè)__block_impl
類型的impl
成員和__main_block_desc_0 *
類型的成員Desc
(顧名思義奋刽,它倆分別代表block的實(shí)現(xiàn)和描述信息)。以及一個(gè)構(gòu)造函數(shù)__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
通過該構(gòu)造函數(shù)艰赞,分別給block的成員賦值。那block的兩個(gè)成員變量的結(jié)構(gòu)又是怎么樣的肚吏,它們里面都有哪些成員呢方妖?
// __block_impld結(jié)構(gòu)體的結(jié)構(gòu)
struct __block_impl {
void *isa; // block的類型
int Flags; // 標(biāo)志位
int Reserved; // 保留位
void *FuncPtr; // block的實(shí)現(xiàn),函數(shù)指針罚攀,指向__main_block_func_0
};
// __main_block_desc_0結(jié)構(gòu)體的結(jié)構(gòu)
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
然后就是__main_block_desc_0
函數(shù)党觅,即block的實(shí)現(xiàn)體。該函數(shù)接受一個(gè)__cself
參數(shù)斋泄,即對(duì)應(yīng)的block自身杯瞻。(** 思考:為什么要傳一個(gè)自身作為參數(shù)? **)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("this is a block");
}
最后看main函數(shù)里block的實(shí)現(xiàn)和調(diào)用:
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);
可以看出執(zhí)行block就是調(diào)用一個(gè)以block自身作為參數(shù)的函數(shù)炫掐,這個(gè)函數(shù)對(duì)應(yīng)著block的執(zhí)行體魁莉。
block為什么能截獲局部變量?
我們來看個(gè)截獲局部變量的block募胃,并轉(zhuǎn)換為中間代碼旗唁,觀察代碼,以嘗試解答這個(gè)問題痹束。
int main()
{
int count = 10;
void (^myBlock)(void) = ^(void){
printf("count = %d", count);
};
myBlock();
return 0;
}
轉(zhuǎn)換后的代碼检疫。只列出發(fā)生了變化的代碼:
可以看到__main_block_impl_0
結(jié)構(gòu)體中多了count
這個(gè)成員變量。并且構(gòu)造函數(shù)的參數(shù)中也多了count這一項(xiàng)祷嘶。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int count;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _count, int flags=0) : count(_count) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到block的實(shí)現(xiàn)體中__main_block_func_0
多了int count = __cself->count;
這一句屎媳。
** block之所以可以截獲局部變量就是因?yàn)?code>__cself訪問了該block里面的count成員變量夺溢,而block的count成員的值是在實(shí)現(xiàn)該block時(shí)賦得的。** 此時(shí)烛谊,前面我們的疑問:這個(gè)函數(shù)“為什么要傳一個(gè)自身作為參數(shù)风响?的問題也迎刃而解,不言而喻了晒来〕睿”之所以該方法要傳代表block結(jié)構(gòu)的__main_block_impl_0
結(jié)構(gòu)體為參數(shù),就是為了讀取該block捕獲的局部變量湃崩。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int count = __cself->count; // bound by copy
printf("count = %d", count);
}
int main()
{
int count = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
block為什么只能讀取局部變量荧降,而不能修改局部變量呢?
因?yàn)閙ain函數(shù)中的局部變量
count
和函數(shù)__main_block_func_0
不在同一個(gè)作用域中攒读,調(diào)用過程中只是進(jìn)行了值傳遞朵诫。當(dāng)然,在上面代碼中薄扁,我們可以通過指針來實(shí)現(xiàn)局部變量的修改剪返。不過這是由于在調(diào)用__main_block_func_0
時(shí),main函數(shù)棧還沒展開完成邓梅,變量count
還在棧中脱盲。但是在很多情況下,block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的日缨。通常在這些情況下钱反,block被執(zhí)行時(shí),定義時(shí)所在的函數(shù)棧已經(jīng)被展開匣距,局部變量已經(jīng)不在棧中了面哥。(不過既然如此,我們可以推斷出靜態(tài)局部變量之所以可以在block修改就是通過——指針毅待。因?yàn)殪o態(tài)局部變量存在于內(nèi)存數(shù)據(jù)段尚卫,不存在棧展開后非法訪存的風(fēng)險(xiǎn)。見下一段尸红。)
所以吱涉,對(duì)于auto類型的局部變量,不允許block進(jìn)行修改是合理的驶乾。
block為什么可以又可以修改靜態(tài)變量和全局變量呢邑飒?
因?yàn)樗鼈儾淮嬖跅U归_后非法訪存的風(fēng)險(xiǎn)。所以可以通過** 指針 ** 來傳遞靜態(tài)變量的级乐。
可以看出靜態(tài)變量在main內(nèi)實(shí)現(xiàn)block時(shí)疙咸,捕獲的是count
的地址&count
。以及在__main_block_impl_0
結(jié)構(gòu)體中成員變量變成了指針類型int *count;
风科。即通過指針修改(它們是址傳遞)撒轮。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *count;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_count, int flags=0) : count(_count) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *count = __cself->count; // bound by copy
printf("count = %d", ++(*count));
}
int main()
{
static int count = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &count));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
為什么被__block修飾的局部變量在block中卻又是可以修改的乞旦?
我們來一段局部變量前加了__block
的代碼例子:
#include "BlockClang.h"
int main()
{
__block int count = 10;
void (^myBlock)(void) = ^(void){
printf("count = %d",++count);
};
myBlock();
}
轉(zhuǎn)換中間代碼后,看到比以前多了很多東西题山。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_count_0 *count; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count, int flags=0) : count(_count->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
struct __Block_byref_count_0 {
void *__isa;
__Block_byref_count_0 *__forwarding;
int __flags;
int __size;
int count;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_count_0 *count = __cself->count; // bound by ref
printf("count = %d",++(count->__forwarding->count));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->count, (void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);}
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_count_0 count = {(void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 10};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_count_0 *)&count, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
可以看到__main_block_impl_0
結(jié)構(gòu)體成員變量count
變?yōu)榱?code>__Block_byref_count_0 *類型兰粉。而相應(yīng)的__main_block_func_0
函數(shù)中count
也變?yōu)榱?code>__Block_byref_count_0 *類型。
__Block_byref_count_0
也是一個(gè)結(jié)構(gòu)體顶瞳。它的構(gòu)成是:
struct __Block_byref_count_0 {
void *__isa;
__Block_byref_count_0 *__forwarding; // 指向另外一個(gè)變量玖姑,這兒的具體實(shí)現(xiàn)思路不太懂
int __flags;
int __size;
int count;
};
** 但是問題照樣存在,我們修改的變量count
它是位于棧上的慨菱。若當(dāng)block被回調(diào)執(zhí)行時(shí)焰络,棧早已被展開,早沒count
了符喝。這該如何是好闪彼?**
上面的代碼中我們可以注意到:__main_block_desc_0
函數(shù)中多了兩個(gè)成員函數(shù),分別指向__main_block_copy_0
协饲,__main_block_dispose_0
函數(shù)畏腕。
當(dāng)block從棧上被copy到堆上時(shí),會(huì)調(diào)用
__main_block_copy_0
將__block
類型的成員變量count
從棧上復(fù)制到堆上茉稠;而當(dāng)block被釋放時(shí)描馅,相應(yīng)地會(huì)調(diào)用__main_block_dispose_0
來釋放__block
類型的成員變量i。
一會(huì)在棧上而线,一會(huì)在堆上流昏,那如果棧上和堆上同時(shí)對(duì)該變量進(jìn)行操作,怎么辦吞获?
這時(shí)候,__forwarding
的作用就體現(xiàn)出來了:當(dāng)一個(gè)__block
變量從棧上被復(fù)制到堆上時(shí)谚鄙,棧上的那個(gè)__Block_byref_i_0
結(jié)構(gòu)體中的__forwarding
指針也會(huì)指向堆上的結(jié)構(gòu)各拷。
資料參考:
iOS中block實(shí)現(xiàn)的探究
C語言中閉包的探究及比較
C語言中閉包的探究及比較
對(duì)Objective-C中Block的追探
談Objective-C block的實(shí)現(xiàn)