前情提要
基于Block原理分析(1),繼續(xù)分析Block
中的剩余知識(shí)點(diǎn)。
1.__block
說(shuō)明符
我們?cè)賮?lái)回顧前面截獲自動(dòng)變量的例子。
^{printf(fmt, capturedVariable);};
該源碼轉(zhuǎn)換結(jié)果如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
Block
中所使用的的被截獲自動(dòng)變量就如"帶有自動(dòng)變量值的匿名函數(shù)"所說(shuō),僅截獲自動(dòng)變量的值菜拓。Block
中使用自動(dòng)變量后传于,在Block
的結(jié)構(gòu)體實(shí)例中重寫(xiě)該自動(dòng)變量也不會(huì)改變?cè)瓉?lái)截獲的自動(dòng)變量治筒。詳細(xì)解釋就是截獲的自動(dòng)變量為__cself->fmt
展箱。而這個(gè)__cself
就是blk
腹殿,而blk
結(jié)構(gòu)體的成員變量fmt
和capturedVariable
是在Block
初始化過(guò)程中独悴,以值傳遞的方式賦值給Block
結(jié)構(gòu)體的成員變量:
//int main 中
int unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));--->Block例书;
之后再將初始化好的Block
的結(jié)構(gòu)體賦值給blk
:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
而下面的函數(shù)調(diào)用(blk->impl.FuncPtr)(blk)
:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
就是把blk
當(dāng)做參數(shù)傳遞給了__main_block_func_0
方法,所以__cself
就是blk
自己刻炒。
如果我們?cè)噲D在Block
內(nèi)部改變被Block
截獲的值會(huì)怎么樣呢?系統(tǒng)會(huì)產(chǎn)生編譯錯(cuò)誤决采。因?yàn)樵趯?shí)現(xiàn)上不能改寫(xiě)被截獲自動(dòng)變量值的值,所以當(dāng)編譯器在編譯過(guò)程中檢查出給被截獲自動(dòng)變量值賦值的操作時(shí)坟奥,便產(chǎn)生了編譯錯(cuò)誤树瞭。這樣一來(lái)就無(wú)法在Block
中保存值了,極為不便爱谁。解決這個(gè)問(wèn)題有兩種方法晒喷。
第一種:C
語(yǔ)言中有三個(gè)變量宿接,允許Block
直接改寫(xiě)其值链快。具體如下:
- 靜態(tài)變量: 在函數(shù)內(nèi)定義,相比較于自動(dòng)變量(生存期為函數(shù)的生存期)劫扒,它的生存期為整個(gè)程序捐顷。但作用域與自動(dòng)變量相同荡陷;
- 靜態(tài)全局變量: 作用域?yàn)槎x它的文件內(nèi),生命周期為定義它文件的生命周期迅涮,大部分情況下同源程序废赞;
- 全局變量: 作用域?yàn)槿抗こ涛募?生命周期為整個(gè)源程序的生命周期;
雖然Block
語(yǔ)法的匿名函數(shù)部分(__main_block_func_0
)變換為了C
語(yǔ)言函數(shù),但從這個(gè)變換的函數(shù)中訪問(wèn)靜態(tài)全局變量叮姑,全局變量并沒(méi)有任何改變唉地,可直接使用。但是靜態(tài)變量的情況下传透,轉(zhuǎn)換后的函數(shù)原本就設(shè)置在含有Block
語(yǔ)法的函數(shù)外(靜態(tài)變量作用域外)耘沼,所以無(wú)法訪問(wèn), 除非將其捕獲。
我們來(lái)看看下面這段源代碼:
#include <stdio.h>
// 全局變量
int global_val = 1;
// 靜態(tài)全局變量
static int static_global_val = 2;
int main() {
// 靜態(tài)局部變量
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
return 0;
}
轉(zhuǎn)換后,摘取有用的部分:
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
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() {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
這個(gè)結(jié)果我們已經(jīng)很熟悉了朱盐,對(duì)靜態(tài)全局變量static_global_val
和全局變量global_val
的訪問(wèn)與轉(zhuǎn)換之前完全相同群嗤。靜態(tài)變量static_val
又是如何轉(zhuǎn)換的呢?以下摘出Block
中使用該變量的部分:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
那這個(gè)__cself->static_val
又是誰(shuí)呢兵琳?
int main() {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
是Block
初始化的時(shí)候的一個(gè)參數(shù)&static_val
狂秘。進(jìn)行了一個(gè)指針傳遞,這是超出作用域使用變量的最簡(jiǎn)單方法躯肌≌叽海可以自己用printf
打印再確認(rèn)一下。實(shí)際上清女,在由Block語(yǔ)法生成的值Block
上钱烟,可以存有超過(guò)其變量作用域的被截獲對(duì)象的自動(dòng)變量。變量作用域結(jié)束的同時(shí),原來(lái)的自動(dòng)變量被廢棄拴袭,因此Block
中超過(guò)變量作用域而存在的變量同靜態(tài)變量一樣传惠,將不能通過(guò)指針訪問(wèn)原來(lái)的自動(dòng)變量。但該變量在Block
內(nèi)仍然可以訪問(wèn)稻扬,這種情況發(fā)生在將Block
從棧復(fù)制到堆上,與此同時(shí)__block
修飾的變量也會(huì)一同被復(fù)制到堆上, Block
持有該對(duì)象羊瘩。
第二種:使用"__block
說(shuō)明符"泰佳。更準(zhǔn)確的表述是:"__block
存儲(chǔ)類(lèi)域說(shuō)明符"(這個(gè)類(lèi)不是面向?qū)ο蟮念?lèi),是類(lèi)域)尘吗。C
語(yǔ)言中有以下存儲(chǔ)類(lèi)域說(shuō)明符:typedef
,extern
,static
,auto
,register
逝她。
__block
說(shuō)明符類(lèi)似于static
,auto
和register
說(shuō)明符,他們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中睬捶,例如黔宛,auto
表示作為自動(dòng)變量存儲(chǔ)在棧中,static
表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)內(nèi)擒贸。
下面我們來(lái)實(shí)際使用__block
說(shuō)明符臀晃,用它來(lái)指定Block
中想變更值的自動(dòng)變量。
.m代碼:
#include <stdio.h>
int main() {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
轉(zhuǎn)換后介劫,摘取又用的部分:
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));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
加一個(gè)__block
相較于之前轉(zhuǎn)換的代碼出現(xiàn)了很多新面孔徽惋,我們一個(gè)一個(gè)地看。
__block int val = 10;
轉(zhuǎn)換后的代碼是:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10};
整體簡(jiǎn)化一下:
__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
我們發(fā)現(xiàn)座韵,這個(gè)自動(dòng)變量變成了一個(gè)結(jié)構(gòu)體實(shí)例险绘。__block
修飾的變量(val
)也同Block
一樣變成__Block_byref_val_0
結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,即棧上生成的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例誉碴,該變量初始化為10
宦棺,并且這個(gè)值(10
)也出現(xiàn)在了結(jié)構(gòu)體實(shí)例的初始化中,這意味著該結(jié)構(gòu)體持有相當(dāng)于原自動(dòng)變量(val
)的成員變量(int val
)黔帕。
該結(jié)構(gòu)體聲明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
接著代咸,給__block
變量賦值的代碼:^{val = 1;};
轉(zhuǎn)換后:
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
中向靜態(tài)變量賦值時(shí),使用了指向該靜態(tài)變量的指針成黄,而向__block
變量賦值要比這個(gè)更為復(fù)雜侣背。Block
的__main_block_impl_0
結(jié)構(gòu)體實(shí)例持有指向__block
變量的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例指針(&val)。
__Block_byref_val_0
結(jié)構(gòu)體實(shí)例的成員變量__forwarding
持有指向該實(shí)例自身的指針(看初始化函數(shù)慨默,__forwarding
的賦值就是&val
)贩耐。通過(guò)成員變量__forwarding
訪問(wèn)成員變量val
。(成員變量int val
是該實(shí)例自身持有的變量厦取,它相當(dāng)于原自動(dòng)變量val
)潮太。
究竟為什么會(huì)有成員變量__forwarding
呢?是為了保證,無(wú)論這個(gè)被捕獲的變量是在棧上還是堆上都能正確地訪問(wèn)這個(gè)變量(唯一性)。
另外铡买,__block
變量的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例attribute((blocks(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 )&val, 0, sizeof(__Block_byref_val_0), 10};
的初始化并不在Block的__main_block_impl_0
結(jié)構(gòu)體中更鲁,而是在int main
中這樣做是為了在多個(gè)Block
中使用__block
變量。有如下.m源碼:
#include <stdio.h>
int main() {
__block int val = 20;
void (^blk0)(void) = ^{
val = 0;
};
void (^blk1)(void) = ^{
val = 1;
};
blk0();
blk1();
return 0;
}
轉(zhuǎn)換后:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
初始化blk0
,blk1
的代碼,都是直接&val(拿的地址),而這個(gè)val
是上面創(chuàng)建的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例
兩個(gè)Block
都使用了__Block_byref_val_0
結(jié)構(gòu)體實(shí)例val
的地址(&val
),這樣就可以在多個(gè)Block中使用同一個(gè)__block
變量了奇钞。當(dāng)然澡为,反過(guò)來(lái)一個(gè)Block也可以使用多個(gè)__block
變量,比如.m:
__block int val = 20;
__block int val1 = 20;
__block int val2 = 20;
void (^blk0)(void) = ^{
val = 0;
val1 = 0;
val2 = 0;
};
轉(zhuǎn)換后:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
__attribute__((__blocks__(byref))) __Block_byref_val1_1 val1 = {(void*)0,(__Block_byref_val1_1 *)&val1, 0, sizeof(__Block_byref_val1_1), 20};
__attribute__((__blocks__(byref))) __Block_byref_val2_2 val2 = {(void*)0,(__Block_byref_val2_2 *)&val2, 0, sizeof(__Block_byref_val2_2), 20};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, (__Block_byref_val1_1 *)&val1, (__Block_byref_val2_2 *)&val2, 570425344));
下面我們來(lái)了解Block
超出變量作用域仍可存在的理由景埃,以及__block
變量的結(jié)構(gòu)體成員變量__forwarding
存在的理由媒至。討論之前,我們先來(lái)看一下Block
的存儲(chǔ)域谷徙。
- 通過(guò)前面說(shuō)明可知拒啰,
Block(^{printf("This is a Block")};)
轉(zhuǎn)換為Block
的結(jié)構(gòu)體類(lèi)型(__main_block_impl_0)
的自動(dòng)變量,__block
變量(__block int val = 20;
)轉(zhuǎn)換為__block
變量的結(jié)構(gòu)體類(lèi)型(__Block_byref_val_0
)的自動(dòng)變量。所謂的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量完慧,就是棧上生成的該結(jié)構(gòu)體的實(shí)例谋旦。另外,通過(guò)之前的說(shuō)明可知Block
也是OC
對(duì)象屈尼。將Block
當(dāng)做OC
對(duì)象來(lái)看時(shí)册着,該Block
的類(lèi)為_NSConcreteStackBlock
。OC
有很多與之類(lèi)似的類(lèi)脾歧,如:_NSConcreteStackBlock
,_NSConcreteGlobalBlock
,_NSConcreteMallocBlock
指蚜。 - 首先我們能夠注意到
_NSConcreteStackBlock
中含有stack
一詞,即該類(lèi)的對(duì)象Block
設(shè)置在棧上涨椒。同樣地摊鸡,_NSConcreteMallocBlock
類(lèi)對(duì)象設(shè)置在由mallo
函數(shù)分配的內(nèi)存塊即堆中,而_NSConcreteGlobalBlock
類(lèi)對(duì)象則設(shè)置在程序的數(shù)據(jù)區(qū)域(.data
區(qū))中蚕冬。 - 到現(xiàn)在為止出現(xiàn)的
Block
例子使用的都是_NSConcreteStackBlock
類(lèi)免猾,且都設(shè)置在棧上,但實(shí)際上并非全是這樣囤热,在記述全局變量的地方使用Block
語(yǔ)法時(shí)猎提,生成的Block
為_NSConcreteGlobalBlock
類(lèi)對(duì)象,例如:void (^blk)(void) = ^{printf("Global Block\n")};
轉(zhuǎn)換后的impl.isa = &_NSConcreteGlobalBlock;
,該Block
的類(lèi)為_NSConcreteGlobalBlock
類(lèi)旁蔼。此Block
設(shè)置在程序的數(shù)據(jù)區(qū)域內(nèi)锨苏。因?yàn)樵谑褂萌肿兞康牡胤讲淮嬖谧詣?dòng)變量,所以不存在對(duì)自動(dòng)變量進(jìn)行截獲一說(shuō)棺聊。也就是說(shuō)Block
用結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴于執(zhí)行時(shí)的狀態(tài)伞租,所以整個(gè)程序中只需一個(gè)實(shí)例。因此限佩,只要不需要用Block
來(lái)截獲自動(dòng)變量葵诈,就可以將Block
用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域內(nèi)裸弦。
那么在Block
配置在堆上的_NSConcreteMallocBlock
類(lèi)對(duì)象是在何時(shí)使用的呢?這正是上一節(jié)最后遺留問(wèn)題的答案作喘。遺留問(wèn)題為:Block
超出變量作用域可存在的原因,和__block
變量用結(jié)構(gòu)體成員變量__forwarding
存在的原因理疙。
- 配置在全局變量上的
Block
,從變量作用域外也可以通過(guò)指針安全地使用泞坦。但設(shè)置在棧上的Block
窖贤。如果其所屬的變量作用域結(jié)束,該Block
就被廢棄贰锁。由于__block
變量也配置在棧上赃梧,同樣地,如果其所屬的變量作用域結(jié)束李根。則該__block
變量也會(huì)被廢棄。 -
Blocks
提供了將__block
變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題几睛。將配置在棧上的Block
復(fù)制到堆上房轿,這樣即使Block
語(yǔ)法記述的變量作用域結(jié)束,堆上的Block
還可以繼續(xù)存在所森。 - 復(fù)制到堆上的
Block
將_NSConcreteMallocBlock
類(lèi)對(duì)象寫(xiě)入Block
用結(jié)構(gòu)體實(shí)例的成員變量isa
囱持。impl.isa = &_NSConcreteMallocBlock;
- 而
__block
變量用結(jié)構(gòu)體成員變量__forwarding
可以實(shí)現(xiàn)無(wú)論__block
變量配置在棧上還是堆上,都能夠正確地訪問(wèn)__block
修飾的變量焕济。有時(shí)在__block
變量配置在堆上的狀態(tài)下纷妆,也可以訪問(wèn)棧上的__block
變量。在此情形下晴弃,只要棧上的結(jié)構(gòu)體成員變量__forwarding
指向堆上的結(jié)構(gòu)體實(shí)例掩幢,那么不管是從棧上的__block
變量還是從堆上的__block
變量都能正確訪問(wèn)。 - 那么
Blocks
提供的復(fù)制方法究竟是什么呢?實(shí)際上當(dāng)ARC有效時(shí)上鞠,大多數(shù)情形下編譯器會(huì)恰當(dāng)?shù)剡M(jìn)行判斷际邻,自動(dòng)生成Block
從棧上復(fù)制到堆上的代碼。
例:
typedef int (^blk_t)(int);
blk_t testFunc(int rate) {
return ^(int count){return rate * count;};
}
非ARC下編譯是不能通過(guò)的芍阎,會(huì)提示:error: returning block that lives on the local stack return ^(int count){return rate * count;};
而ARC環(huán)境下的代碼會(huì)被編譯為:
blk_t testFunc(int rate) {
blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
由objc4
運(yùn)行時(shí)庫(kù)可知世曾,objc_retainBlock
函數(shù)實(shí)際上就是__Block_copy
函數(shù)。
仔細(xì)分析這三步都發(fā)生了什么:
1.blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);
將通過(guò)Block
語(yǔ)法生成的Block(&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate))
賦值tmp
谴咸。
2.接下來(lái)tmp = objc_retainBlock(tmp)
;相當(dāng)于tmp = __Block_copy(tmp);
就是把棧上的tmp
也就是原來(lái)生成的Block
結(jié)構(gòu)體實(shí)例轮听,復(fù)制到堆上,再把堆上的這個(gè)Block
的地址賦值給tmp
×爰眩現(xiàn)在tmp
所指的就是堆上的Block
結(jié)構(gòu)體實(shí)例血巍。
3.最后return objc_autoreleaseReturnValue(tmp);
將堆上的結(jié)構(gòu)體實(shí)例作為對(duì)象,注冊(cè)到autoreleasepool
中然后返回該對(duì)象珊随。即藻茂,ARC
下將Block
作為函數(shù)返回值,編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。
那什么情況下是編譯器不能恰當(dāng)判斷的呢辨赐?
就是向方法或函數(shù)的參數(shù)中傳遞Block
時(shí)优俘。但如果在方法或函數(shù)中適當(dāng)?shù)貜?fù)制了傳遞過(guò)來(lái)的參數(shù)(Block
),那么就不必再調(diào)用該方法或函數(shù)手動(dòng)復(fù)制了掀序。比如Cocoa
框架的方法且方法名中含有usingBlock
的帆焕,以及GCD
的API
。其他情況下要記得加copy
,([XXBlock copy];
)不恭。另外對(duì)于已配置在堆上和數(shù)據(jù)區(qū)域的Block
調(diào)用copy
方法是叶雹,前者引用計(jì)數(shù)會(huì)加1,后者什么也不做换吧。但無(wú)論Block
配置在何處折晦,用copy
方法都不會(huì)引起任何問(wèn)題。在不確定時(shí)沾瓦,調(diào)用copy
方法即可满着。在ARC
下即便多次調(diào)用copy
,編譯器也能正確地控制引用計(jì)數(shù)贯莺,所以沒(méi)問(wèn)題风喇。
__block變量存儲(chǔ)域
若在一個(gè)Block
中使用__block
變量,則當(dāng)Block
從棧上復(fù)制到堆上時(shí)缕探。這些__block
變量也全部被從棧復(fù)制到堆魂莫。此時(shí),Block
持有__block
變量爹耗。即使在該Block
已復(fù)制到堆上的情形下耙考,復(fù)制Block
也對(duì)所使用的__block
變量沒(méi)有任何影響。在多個(gè)Block
中使用__block
變量時(shí)潭兽,因?yàn)樽钕葧?huì)將所有的Bloc
k配置在棧上琳骡,所以__block
變量也會(huì)配置在棧上。在任何一個(gè)Block
從棧復(fù)制到堆時(shí)讼溺,__block
變量也會(huì)一并從棧復(fù)制到堆并被該Block
所持有楣号。當(dāng)剩下的Block
從棧復(fù)制到堆時(shí),被復(fù)制的Block
持有__block
變量怒坯,并增加__block
變量的引用計(jì)數(shù)炫狱。如果配置在堆上的Block
被廢棄,那么它所使用的__block
變量也就被釋放剔猿。
聊聊__forwarding
的作用:不管__block
變量配置在棧上還是在堆上视译,都能夠正確地訪問(wèn)該變量」榫矗看如下.m代碼:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
NSLog(@"%d", val);
blk();
NSLog(@"%d", val);
//外層++val
++val;
NSLog(@"%d", val);
輸出是0 1 2酷含。
轉(zhuǎn)換后的代碼:
//初始化__block變量的結(jié)構(gòu)體實(shí)例:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
0
};
//初始化Block用結(jié)構(gòu)體并copy再賦值給blk:
void (*blk)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344)), sel_registerName("copy"));
//執(zhí)行blk的方法:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
//blk中的方法:
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
++(val->__forwarding->val);
}
//執(zhí)行外層val++:
++(val.__forwarding->val);
//最后打印val:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_937775_mi_0, (val.__forwarding->val));
當(dāng)初始化__block
結(jié)構(gòu)體實(shí)例的時(shí)候鄙早,__forwarding
指向的是他自己(val棧
);copy
的時(shí)候椅亚,會(huì)把Block
復(fù)制到堆上限番,同時(shí)也把__block
結(jié)構(gòu)體實(shí)例復(fù)制到堆上,并且棧上的__block
的成員變量__forwarding
會(huì)指向堆上的自己(val堆
)呀舔。執(zhí)行Block
內(nèi)部的++(val(棧
)->__forwarding
->val(堆)
)弥虐。執(zhí)行外層val++
:++(val(棧)
->__forwarding
->val(堆)
)。打印:val(棧).__forwarding->val(堆)
媚赖。通過(guò)該功能霜瘪,無(wú)論是在Block
語(yǔ)法中,Block
語(yǔ)法外使用__block
變量惧磺,還是__block
變量配置在棧上或者堆上颖对,都可以順利訪問(wèn)同一個(gè)__block
變量。
截獲對(duì)象
.m代碼:
typedef void (^blk_t)(id);
blk_t blk;
{
id testArray = [NSMutableArray new];
blk = [^(id obj){
[testArray addObject:obj];
NSLog(@"%lu", (unsigned long)[testArray count]);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
轉(zhuǎn)換后磨隘,截取有用的代碼:
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
id testArray;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, id _testArray, int flags=0) : testArray(_testArray) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0 (struct __ViewController__viewDidLoad_block_impl_0 *__cself, id obj) {
id testArray = __cself->testArray; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)testArray, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_c6f3a9_mi_0, (unsigned long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)testArray, sel_registerName("count")));
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->testArray, (void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
//使用部分:
typedef void (*blk_t)(id);
blk_t blk;
{
id testArray = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, testArray, 570425344)), sel_registerName("copy"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
OC
的運(yùn)行時(shí)庫(kù)能夠準(zhǔn)確把握Block
從棧復(fù)制到堆以及堆上的Block
被廢棄的時(shí)機(jī)缤底,使用__ViewController__viewDidLoad_block_desc_0
結(jié)構(gòu)體中增加的成員變量copy
和dispose
,以及作為指針賦值給該成員變量的__ViewController__viewDidLoad_block_copy_0
函數(shù)和__ViewController__viewDidLoad_block_dispose_0
函數(shù)。__ViewController__viewDidLoad_block_copy_0
函數(shù)使用_Block_object_assign
函數(shù)將對(duì)象類(lèi)型對(duì)象賦值給Block
用結(jié)構(gòu)體的成員變量testArray
中并持有該對(duì)象琳拭。用來(lái)管理賦值給Block
用結(jié)構(gòu)體中的testArray
的對(duì)象训堆。相當(dāng)于retain
實(shí)例方法描验,將對(duì)象賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中白嘁。另外_Block_object_dispose
用來(lái)釋放testArray
中的對(duì)象,相當(dāng)于release
實(shí)例方法膘流,釋放賦值在Block
用結(jié)構(gòu)體成員變量testArray
中的對(duì)象絮缅。在Block
從棧上復(fù)制到堆時(shí),以及堆上的Block
被廢棄時(shí)才會(huì)調(diào)用這兩個(gè)函數(shù)呼股。
Block復(fù)制到堆上的時(shí)機(jī):
- 調(diào)用Block
的copy
方法;
- Block
作為函數(shù)返回值時(shí);
- 將Block
賦值給附有__strong
修飾符id
類(lèi)型的類(lèi)或是Block
類(lèi)型成員變量時(shí);
- 在方法名中含有usingBlock
的Cocoa
框架方法或GCD
的API
中傳遞Block
時(shí);
總結(jié)
_Block_copy
函數(shù)被調(diào)用時(shí)Block
被從棧上復(fù)制到堆上耕魄。通過(guò)這種方式,Block
截獲的對(duì)象就能夠超出其變量作用域而存在彭谁。在Block
中使用__block
時(shí)吸奴,也會(huì)有這兩個(gè)方法,不同的是截獲對(duì)象是:BLOCK_FIELD_IS_OBJECT
,截獲__block
自動(dòng)變量是:BLOCK_FIELD_IS_BYREF
缠局。通過(guò)這兩個(gè)參數(shù)區(qū)分對(duì)象類(lèi)型還是__block
變量则奥。由此可知,Block
中使用的賦值給附有__strong
修飾符的自動(dòng)變量的對(duì)象和復(fù)制到堆上__block
變量由于被堆上的Block
所持有狭园,因而可以超出其變量作用域而存在读处。
附加:Block的循環(huán)引用
typedef void (^blk_t)(void);
@interface MyObject: NSObject {
blk_t blk;
}
@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"self = %@", self)};
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
Block
賦值給了對(duì)象的成員變量blk_
,相當(dāng)于該Block
自動(dòng)調(diào)用了copy
方法,Block
被從棧復(fù)制到堆上唱矛,而且Block
還截獲了__strong
修飾符修飾的self
對(duì)象,self
一并被復(fù)制到堆上并被Block
所持有罚舱。因此井辜,對(duì)象持有blk_(Block)
,Block
持有對(duì)象(MyObject
)管闷,形成了循環(huán)引用粥脚,此時(shí),不會(huì)調(diào)用對(duì)象的dealloc
方法渐北。為避免這種循環(huán)引用阿逃,id __weak tmp = self
,之后Block
中使用tmp
就沒(méi)問(wèn)題了。
另外也可以使用__block
變量來(lái)避免循環(huán)引用赃蛛,但需要在Block
體中恃锉,給該變量賦值nil
。并執(zhí)行改Block
呕臂。如不執(zhí)行Block
破托,將該變量賦值為nil
,仍然會(huì)引發(fā)循環(huán)引用歧蒋。Block
持有__block
變量,__block
變量持有self
,self
持有Block
土砂。若執(zhí)行了__block
變量等于nil
,__block
變量就不持有self
了,從而打破循環(huán)谜洽。
使用__block
打破循環(huán)的優(yōu)點(diǎn)在于執(zhí)行Block
時(shí)可動(dòng)態(tài)決定是否將nil
或是其他對(duì)象賦值在__block
變量中萝映。缺點(diǎn)是,為避免循環(huán)引用必須要執(zhí)行Block
阐虚。