__block說(shuō)明符
Block只能保存局部變量瞬間的值,所以當(dāng)我們嘗試修改截獲的自動(dòng)變量值,就會(huì)報(bào)錯(cuò)。例如:
int val = 0;
void (^blk)(void) = ^(val = 1);
blk();
printf("val = %d\n", val);
該源代碼會(huì)產(chǎn)生編譯錯(cuò)誤:
error: variable is not assignable (missing __block type specifier)
因此,若想修改截獲的局部變量值,就必須用__block修飾
__block int val = 0;
void (^blk)(void) = ^(val = 1);
blk();
printf("val = %d\n", val);
執(zhí)行結(jié)果為:
val = 1;
下面我們?cè)倏匆粋€(gè)例子:
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
這是沒(méi)問(wèn)題的。如果向截獲的變量array賦值則會(huì)產(chǎn)生編譯錯(cuò)誤。
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
array = [[NSMutableArray alloc] init];
};
同樣,使用__block修飾array就行了。
另外在使用c語(yǔ)言數(shù)組時(shí),必須小心使用其指針。
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
看似沒(méi)有向截獲的自動(dòng)變量賦值,只是使用了字符串?dāng)?shù)組。但是Block并沒(méi)有實(shí)現(xiàn)截獲c語(yǔ)言數(shù)組。此時(shí)可以使用指針解決問(wèn)題
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
Block的實(shí)質(zhì)
我們通過(guò)一個(gè)實(shí)例來(lái)看看block的實(shí)質(zhì)
void (^blk)(void) = ^{
printf("Block\n");
};
通過(guò)clang來(lái)轉(zhuǎn)換為c++的代碼
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
我們看一下轉(zhuǎn)換后的代碼:
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("Block\n");
}
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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//使用block
(void (*) (struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk)
}
我們可以看到Block的匿名函數(shù)轉(zhuǎn)化為c語(yǔ)言函數(shù)來(lái)處理:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
__cself是指向?qū)嵗陨淼淖兞?code>self。
__main_block_impl_0結(jié)構(gòu)體聲明如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//這是__main_block_impl_0的構(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)體由__block_impl和__main_block_desc_0兩個(gè)成員變量和一個(gè)構(gòu)造函數(shù)組成。
其中__block_impl結(jié)構(gòu)體聲明如下:
struct __block_impl {
void *isa;
int Flags;//標(biāo)志
int Reserved;//今后版本升級(jí)所需的區(qū)域
void *FuncPtr;//函數(shù)指針
};
第二個(gè)成員變量為Desc指針,聲明如下:
struct __main_block_desc_0 {
unsigned long reserved;//今后版本升級(jí)所需的區(qū)域
unsigned long Block_size//Block的大小
}
因此,如果展開(kāi)__main_block_impl_0,可記述如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;//標(biāo)志
int Reserved;//今后版本升級(jí)所需的區(qū)域
void *FuncPtr;//函數(shù)指針
struct __main_block_desc_0 * Desc
};
接下來(lái)看看__main_block_impl_0的構(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;
}
_NSConcreteStackBlock用來(lái)初始化block_impl結(jié)構(gòu)體的isa成員變量
flags用于初始化block_impl結(jié)構(gòu)體的flags
fp用于初始化block_impl結(jié)構(gòu)體的FuncPtr
接下來(lái)我們?cè)倏匆幌略瓉?lái)構(gòu)造函數(shù)的調(diào)用:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
該源代碼將__main_block_impl_0結(jié)構(gòu)體類型的變量(即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針)賦值給blk。
然后再看看是如何使用block的
(void (*) (struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
去掉轉(zhuǎn)換部分:
(*blk->impl.FuncPtr)(blk);
就是通過(guò)函數(shù)指針FuncPtr來(lái)調(diào)用blk本身。
這也證明了__main_block_func_0的參數(shù)__cself指向block值。
直到現(xiàn)在我們明白了block的本質(zhì):
- block本質(zhì)就是一個(gè)__main_block_impl_0。
- block通過(guò)內(nèi)部的函數(shù)指針FuncPtr來(lái)調(diào)用它本身。
還有一點(diǎn)剛才沒(méi)有說(shuō)明,剛才在初始化__block_impl 的isa成員變量的_NSConcreteStackBlock又是什么呢???
要理解_NSConcreteStackBlock,我們結(jié)合objc_object,它也有一個(gè)isa指針,用于指向該對(duì)象所屬的類。
同理:在將Block作為對(duì)象處理時(shí),__block_impl的isa指針指向的類的信息保存在_NSConcreteStackBlock上。即它是在棧上生成的__block_impl結(jié)構(gòu)體實(shí)例。
截獲自動(dòng)變量值
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
blk();
return 0;
}
clang之后:
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val);
}
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 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));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們先看一下不同之處:
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;
}
};
Block將所使用的局部變量作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中。
注意:未使用的的變量將不會(huì)追加。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)
將追加的局部變量作為參數(shù)來(lái)初始化結(jié)構(gòu)體。
__main_block_impl_0展開(kāi)代碼如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = _main_block_func_0;
Desc =&__main_block_desc_0_DATA;
fmt = "val = %d\n"
由此可見(jiàn),在__main_block_impl_0實(shí)例中,自動(dòng)變量被截獲。
我們?cè)賮?lái)看看Block的實(shí)現(xiàn):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val);
}
因?yàn)開(kāi)_main_block_impl_0截獲了fmt和val變量,所以就可以直接執(zhí)行了。
總的來(lái)說(shuō),所謂“截獲自動(dòng)變量值”意味著在執(zhí)行block語(yǔ)法時(shí),block語(yǔ)法所使用的局部變量被保存到block的結(jié)構(gòu)體實(shí)例中。
__block說(shuō)明符
__block說(shuō)明符類似于static、auto,他們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。auto表示作為局部變量存儲(chǔ)在棧中,static表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中。
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
該代碼編譯后如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
}
這個(gè)__block變量是怎樣轉(zhuǎn)換過(guò)來(lái)的呢?
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
__block居然變成了結(jié)構(gòu)體類型的局部變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例。
它包含原自動(dòng)變量的成員變量val
我們?cè)賮?lái)看看__main_block_impl_0這個(gè)結(jié)構(gòu)體有什么不同
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
新增了一個(gè)成員變量__Block_byref_val_0,將這個(gè)成員變量作為參數(shù)來(lái)初始化這個(gè)結(jié)構(gòu)體
__block賦值的代碼又如何呢?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
__block變量賦值比較復(fù)雜,__main_block_impl_0結(jié)構(gòu)體實(shí)例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針。
__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有該實(shí)例自身的指針。通過(guò)成員變量__forwarding訪問(wèn)成員變量val。
至此為止,我們只是大概了解了__block類型,但是還有兩個(gè)問(wèn)題沒(méi)有解決:
*Block超出變量作用域可存在的理由
- __block結(jié)構(gòu)體中__forwording成員變量是干嘛的。
我們繼續(xù)分析。
Block存儲(chǔ)域
由前面的講解,我們知道和
Block的本質(zhì):棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例
__block的本質(zhì):棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例
我們之前說(shuō)過(guò)Block其實(shí)也是一個(gè)OC對(duì)象,它所屬的類是_NSConcretStackBlock,除了這個(gè),還有其他兩個(gè):
- _NSConcretStackBlock:設(shè)置在棧上
- _NSConcretGlobalBlock:設(shè)置在程序的數(shù)據(jù)區(qū)域(.data)中
- _NSConcretMallocBlock:設(shè)置在堆上
配置在全局變量的block,從變量作用外也可以通過(guò)指針安全的使用。
配置在棧上的block,如果其所屬的變量作用域結(jié)束,該block就會(huì)被廢棄。由于__block變量也配置在棧上,如果其所屬的變量作用域也結(jié)束了,__block變量也會(huì)被廢棄。
什么時(shí)候可以設(shè)置在程序的數(shù)據(jù)區(qū)域中呢?
- 使用的block是全局的block
- 即使不是全局的block,如果不截獲的自動(dòng)變量,也會(huì)設(shè)置在.data區(qū)。
除以上這兩種情況,Block都是_NSConcretStackBlock類對(duì)象,設(shè)置在棧上。
現(xiàn)在我們回答第一個(gè)問(wèn)題:Block超出變量作用域可存在的理由??
因?yàn)锽locks提供了將Block和__block變量從棧上復(fù)制到對(duì)上的辦法來(lái)解決這個(gè)問(wèn)題。將配置在棧上的Block復(fù)制到堆上,這樣即使Block變量作用域結(jié)束,堆上的Block也可以繼續(xù)存在。
復(fù)制到堆上的Block就是_NSConcretMallocBlock類對(duì)象。
impl.isa = &_NSConcretMallocBlock
接下來(lái)我們重點(diǎn)看看Blocks提供的復(fù)制方法:
實(shí)際上在ARC下,大多數(shù)情形下編譯器會(huì)自動(dòng)的判斷,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼。我們看一下將Block作為函數(shù)值返回的代碼。
typedef int (^blk_t) (int);
blk_t func(int rate) {
return ^(int count) {
return rate * count;
};
}
該源代碼返回配置在棧上的Block,當(dāng)變量作用域結(jié)束時(shí),這個(gè)Block就會(huì)被廢棄。雖然如此,但該源代碼通過(guò)編輯器可轉(zhuǎn)換如下:
blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValur(tmp);
}
objc_retainBlock就是_Block_copy函數(shù)。
因此當(dāng)Block作為函數(shù)值返回時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。
如果編譯器不能生成復(fù)制到堆上的代碼,就需要手動(dòng)調(diào)用alloc/new/copy/mutableCopy的任意一個(gè)代碼。
哪些情況編譯器會(huì)自動(dòng)將block從棧復(fù)制到堆上:
- 使用Block的copy實(shí)例方法。
- Block作為函數(shù)返回值返回時(shí)。
- 將Block賦值給附有__strong修飾符的id類型的類或者Block類型的實(shí)例變量時(shí)。
- 在方法名中使用usingBlock或者使用GCD的API中傳遞Block時(shí)
第二個(gè)問(wèn)題:__block結(jié)構(gòu)體中__forwording成員變量是干嘛的??
它可以實(shí)現(xiàn)無(wú)論__block變量配置在棧上還是堆上都能夠正確訪問(wèn)__block變量。
有時(shí)__block變量配置在堆上,也可以訪問(wèn)棧上的__block變量,這是因?yàn)闂I系慕Y(jié)構(gòu)體成員變量__forwarding指向堆上的結(jié)構(gòu)體成員變量。