Block基礎(chǔ)回顧
1.什么是Block肤无?
帶有局部變量的匿名函數(shù)(名字不重要牙勘,知道怎么用就行)论泛,差不多就與C語言中的函數(shù)指針類似揩尸,可以當(dāng)做參數(shù)傳來傳去,而且可以沒有名字屁奏。
2.Block語法完整的形式的Block語法如下,格式一
并且與一般的C語言函數(shù)定義相比岩榆,僅有兩點不同:
- 沒有函數(shù)名
- 帶有"^"符號所以根據(jù)前面的語法格式可以寫出如下例子:
^int(int count) {return count+1};
當(dāng)然,也可以有很多的省略格式坟瓢,省略返回值如下圖:
^(int count) {return count+1};
省略返回值類型時勇边,如果表達(dá)式中有return語句時,block語句的的返回值類型就使用return返回的類型折联;如果return中沒有返回類型粒褒,就使用void類型。再省略參數(shù)列表诚镰,參數(shù)列表和返回值都省略是最簡潔的方式奕坟,同時將參數(shù)和返回值省略如下圖:
^{printf("good!");}
3.Block類型變量在Block語法下祥款,一旦使用了Block語法就相當(dāng)于生成了可賦值給Block類型變量的值,"Block"既指源代碼中的Block語法月杉,也指由Block語法所生成的值即:
int (^blk)(int) = ^(int count){return count +1};
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
從上面看出刃跛,Block確實代表了一種語法,但在這里苛萎,對于blk桨昙,他也是一個Block類型變量的值。但是腌歉,當(dāng)Block作為函數(shù)的參數(shù)或者返回 值的時候若傳來傳去蛙酪,寫法上難免有點復(fù)雜,畢竟都是那么長一串兒究履,此時滤否,就可以像C語言那樣使用typedef了:
typdef int(^blk_t)(int);
這時,blk_t就變成了一種Block類型了最仑,例如:
typef int(^blk_t)(int);
blk_t bk = ^(int count){return count+1};
//很明顯省略了返回值
4.截獲的自動變量(自動變量==局部變量)
通過前面的知識,我們已經(jīng)大部分理解了Block了炊甲,這里引入截獲的自動變量泥彤,什么是截獲的局部變量?先看一段代碼:
int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{printf(fmt, val);}; val = 2; fmt = "These values were changed. val = %d\n"; blk(); return 0; }
執(zhí)行結(jié)果:val = 10
解釋:在該源代碼中卿啡,Block語法的表達(dá)式使用的是它之前聲明的自動變量fmt 和val.Block語法中吟吝,Block表達(dá)式截獲的自動變量,級保存該自動變量瞬間的值颈娜。因為Block表達(dá)式保存了自動變量的值剑逃,所以在執(zhí)行Block語法之后,即使概念Block中的自動變量的值也不會影響B(tài)lock執(zhí)行時自動變量的值官辽。這就是所謂的截獲
5._ _block修飾符咱們來嘗試著蛹磺,在Block中修改自動變量的值:
int val = 0; void (^blk)(void) = ^{val = 1;}; blk(); printf("val = %d\n", val);
執(zhí)行結(jié)果:error: variable is not assignable (missing __block type specifier) void (^blk)(void) = ^{val = 1;};~~~ ^
很顯然,光這樣的話是不允許在Block內(nèi)部修改外面的自動變量的值的同仆。如果強勢要改呢萤捆,所以這會兒就該_ _block出場了:若想在Block語法的表達(dá)式中將賦值給在Block語法外聲明的自動變量,需要在該自動變量上加上 _ _block修飾符俗批,如下:
__block int val = 0; void (^blk)(void) = ^{val = 1;}; blk(); printf("val is %d",val);
執(zhí)行結(jié)果:val is 1
所以俗或,使用_ block修飾的變量,就可以在Block語法內(nèi)部進行修改了岁忘,該變量稱為 _block變量辛慰。但這里還有另一種情況,見如下代碼:
id array = [[NSMutableArray alloc] init]; void (^blk)(void) = ^{id obj = [[NSObject alloc] init]; [array addObject:obj]; };
這會出錯嗎干像?其實是不會的帅腌,咱們在這里是沒有向arry賦值辱志,向他賦值才會產(chǎn)生編譯錯誤,在這里狞膘,咱們截獲到了NSMutableArray類對象的一個結(jié)構(gòu)體指針(后面會講)揩懒,咱們沒有對它賦值,只是使用而已挽封,所以不會出錯已球。
Block的實現(xiàn)
1.Block的實質(zhì)
Block看上去看特別,但實際上是作為極普通的C語言源代碼來處理的辅愿,通過支持Block的編譯器智亮,含有Block語法的源代碼轉(zhuǎn)換為一般C語言編譯器能夠處理的代碼,并作為極普通的C語言代碼來編譯点待。概念上可以這樣理解阔蛉,但在實際編譯時無法轉(zhuǎn)化為我們能夠理解的源代碼,但是Apple的LLVM具有轉(zhuǎn)換為我們可讀源代碼的功能轉(zhuǎn)換為C++.說是C++,其實也是使用了struct的結(jié)構(gòu)癞埠,其本質(zhì)還是C語言.
下面引入在《Pro Multithreading and Memory Management for iOS and OS X with ARC, Grand Central Dispatch, and Blocks》中提到的一段源代碼:
int main() { void (^blk)(void) = ^{printf("Block\n");}; blk(); return 0; }
經(jīng)過轉(zhuǎn)換后:
struct __block_impl
{
void *isa;
int Flags;
int Reserved;
void FuncPtr;
};
struct __main_block_impl_0
{
struct __block_impl imply;
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 {
unsigned long reserved;
unsigned long Block_size;
}
__main_block_desc_0_DATA = {
0,sizeof(struct __main_block_impl_0)
};
int main()
{
void (blk)(void) =
(void ()(void))&__main_block_impl_0(
(void )__main_block_func_0, &__main_block_desc_0_DATA);
((void ()(struct __block_impl *))(
(struct __block_impl *)bulk)->FuncPtr)((struct __block_impl *)bulk);
return 0; }
幾個重要的的已用MarkDown下面的代碼格式標(biāo)記了出來状原,略過所有的講解過程,直接拋出結(jié)論:
void (^blk)(void) = ^{printf("Block\n");};
- 1.將Block語法生成的Block值賦值給Block類型的變量blk,實質(zhì)上是將
struct __main_block_impl_0
結(jié)構(gòu)體實例的指針賦給變量blk苗踪,該源代碼中的Block就是struct __main_block_impl_0結(jié)構(gòu)體類型的自動變量颠区,即在棧上生成的__main_block_impl_0結(jié)構(gòu)體實例。- 2.上面的
struct __block_impl
,struct __main_block_desc_0
,__main_block_func_0
都是為了__main_block_impl_0
構(gòu)造函數(shù)或者成員變量而準(zhǔn)備的參數(shù)通铲。- 3.
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;}
結(jié)構(gòu)體包含了兩部分毕莱,impl對應(yīng)在內(nèi)存中的實現(xiàn),desc對應(yīng)于實現(xiàn)所需要的資源颅夺。
將struct __main_block_impl_0 結(jié)構(gòu)體全部展開朋截,將其明顯的顯示出來:
truct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0* Desc; }
該結(jié)構(gòu)體根據(jù)構(gòu)造函數(shù)會想下面這樣進行初始化:
isa = &_NSConcreteStackBlock; Flags = 0; Reserved = 0; FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA;
blk();
//調(diào)用的時候,轉(zhuǎn)為其在C++下面的實現(xiàn)其實是:
(*blk->impl.FuncPtr)(blk);
至此吧黄,基本上已經(jīng)摸清了Block的實質(zhì)部服,唯一還有個未解決的問題就是isa = &_NSConcreteStackBlock,所以稚字,下面來解釋解釋
2.&_NSConcreteStackBlock 饲宿, 類與對象的runtime關(guān)系
為了解決這個問題,首先得搞清楚Runtime下類和對象的實質(zhì)胆描,其實Block就是Objective-C對象瘫想。"id"這一變量類型用于存儲ObjectIve-C對象,在ObjectIve-C源代碼/usr/include/objc/runtime.h中如下聲明:
typedef struct objc_object { Class isa; } *id;
//再看看class
typdef struct objc_class *Class
Class為指向objc_class結(jié)構(gòu)體的指針類型
objc_class結(jié)構(gòu)體在/usr/include/objc/runtime.h中如下聲明:>struct objc_class { Class isa;};
所以objc_class中也有一個指向自己所代表的類昌讲,“Objective-C中由類生成對象”国夜,意味著,對于結(jié)構(gòu)體來說短绸,類對應(yīng)的結(jié)構(gòu)體應(yīng)該“生成由該類生成的對象的結(jié)構(gòu)體實例”车吹,通過每個結(jié)構(gòu)體的成員變量isa保持該類的結(jié)構(gòu)體實例指針筹裕,即維持類與對象的關(guān)系鏈:
各類的結(jié)構(gòu)體就是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體(前面提到的objc_class結(jié)構(gòu)體不是指的類所對應(yīng)的結(jié)構(gòu)體,別混了)窄驹,接下來看看class_t結(jié)構(gòu)體在obj4y運行時庫的runtime/objc-runtime-new.h中聲明如下:
struct class_t { struct class_t *isa; struct class_t *superclass; Cache cache; IMP *vtable; uintptr_t data_NEVER_USE; };
在ObjectIve-C中朝卒,比如NSObject的class_t的結(jié)構(gòu)體實例以及NSMtableArray的class_t結(jié)構(gòu)體實例等,均能生成保持各個類的class_t結(jié)構(gòu)體實例乐埠。該實例持有聲明的成員變量抗斤,方法的名稱,方法的實現(xiàn)(即函數(shù)指針),屬性以及父類的指針丈咐,并被Objective-C運行時庫所使用瑞眼。所以這里簡單的提了一下類與對象,這個時候就可以繼續(xù)回到我們之前想解決的問題了,看一下Block對應(yīng)于內(nèi)存中的結(jié)構(gòu)體:
struct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0* Desc; }
是不是和之前的對象結(jié)構(gòu)體很類似棵逊,其實伤疙,此struct __main_block_impl_0結(jié)構(gòu)體就是相當(dāng)于之前Objective-C對象的結(jié)構(gòu)體objc_object,另外對其成員變量的isa做了一個初始化:
isa = &_NSConcreteStackBlock;
根據(jù)之前的類比,這個_NSConcreteStackBlock是不是就相當(dāng)于class_t結(jié)構(gòu)體實例了呢辆影。也就是說徒像,在將Block作為Objective-C的對象處理時,關(guān)于類的信息就在_NSConcreteStackBlock中秸歧。所以就理解Block也是ObjectIve-C對象了厨姚。
3.截獲自動變量值的實現(xiàn)
回到之前最開始將截獲自動變量值的時候那個例子:
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
同樣,轉(zhuǎn)換一下代碼:
struct __main_block_impl_0
{
struct __block_impl imply;
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->fat;
int val = __cself->val;
printf(fmt, val);
}
static struct __main_block_desc_0
{
unsigned long reserved;
unsigned long 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) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
return 0;
}
__main_block_impl_0結(jié)構(gòu)體內(nèi)聲明的成員變量類型與自動變量類型完全相同键菱,但是注意Block語法表達(dá)式中沒有使用的自動變量不會追加,如此源代碼中的變量dmy:如下今布,所以只有fmt,和经备,val
struct __main_block_impl_0 { struct __block_impl imply; struct __main_block_desc_0* Desc; const char *fmt;int val;};
查看 __main_block_impl_0 的構(gòu)造函數(shù):
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "val = %d\n";
val = 10`
由此可知,在main_block_impl_0結(jié)構(gòu)體實例中部默,自動變量值被截獲侵蒙。總的來說,所謂“截獲自動變量值”意味著在執(zhí)行Block語法時傅蹂,Block語法表達(dá)式所使用的自動變量值被保存到Block的結(jié)構(gòu)體實例中
4.__block說明回顧一下之前的例子:
^{printf(fmt, val);}
該代碼轉(zhuǎn)換結(jié)果如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; int val = __cself->val; printf(fmt, val);}
從代碼中我們可以發(fā)現(xiàn)纷闺,block在截獲自動變量之后,函數(shù)體部分只會改變在Block結(jié)構(gòu)體實例中的那個副本份蝴,并不會改變原先截獲的那個自動變量犁功。在詳細(xì)介紹__block之前,其實我們還有很多種方式來改變block之外的變量例如:靜態(tài)局部變量婚夫,靜態(tài)全局變量浸卦,全局變量,實現(xiàn)過程由于篇幅限制就只說明一下結(jié)果:
1.對于全局變量來說案糙,改變肯定是沒有問題的限嫌,block根本不需要截獲靴庆,在改變的時候直接修改即可。
2.對于靜態(tài)局部變量怒医,是需要截獲的炉抒,只是截獲之后,截獲的是該靜態(tài)變量的指針稚叹,后來改變的時候直接通過指針本身來改變原值焰薄。
3.我們依然要使用__block描述的原因是,對于靜態(tài)局部變量來說(其余兩種方式無影響)入录,即使是靜態(tài)局部變量蛤奥,在局部變量超過其處理域的時,是要被廢棄掉的僚稿,但是對于Block來說凡桥,在由Block語法生成的Block上,是可以存超過其變量作用域名而存在的變量的蚀同。所以導(dǎo)致的結(jié)果就是缅刽,Block中存著變量的地址,但地址所對應(yīng)的變量本身已經(jīng)被廢棄蠢络,所以就不能通過指針來訪問原數(shù)據(jù)了衰猛。
接下來,終于可以引進__block說明符了:
大伙都知道C語言中的存儲域說明符刹孔,如 extern 啡省, static ,auto 髓霞,register均用于指定變量值設(shè)置到哪個存儲區(qū)中卦睹,例如,auto表示作為自動變量存儲在棧中方库,static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)中结序,其實__block說明符也是類似于這幾個,用于指定變量存在哪個存儲區(qū)纵潦。下面我們來實際使用__block說明符:
__block int val = 10;void (^blk)(void) = ^{val = 1;};
將代碼轉(zhuǎn)換后如下:
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 imply;
struct __main_block_desc_0 Desc;
__Block_byref_val_0 *val;
__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; (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(&dst->val, src->val, BLOCK_FIELD_IS_BYREF); } static void __main_block_dispose_0
(struct __main_block_impl_0src)
{
_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0` {
unsigned long reserved;
unsigned long 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()
{
__Block_byref_val_0 val = { 0,&val,0, sizeof(__Block_byref_val_0), 10};
blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
增加了__block之后徐鹤,代碼一下子劇增,與前面不同的是邀层,這里截獲的變量居然就變成了一個結(jié)構(gòu)體返敬,即棧上生成了一個__Block_byref_val_0結(jié)構(gòu)體實例,并且在這個結(jié)構(gòu)體中也持有相當(dāng)于原自動變量的成員變量把該結(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; (val->__forwarding->val) = 1; }
通過構(gòu)造函數(shù)也可以看到救赐,__Block_byref_val_0結(jié)構(gòu)體的成員變量__forwarding持有指向該實例自身的指針,通過成員變量__forwarding訪問成員變量val(成員變量是該實例自身持有的變量,它相當(dāng)于原自動變量)
總結(jié):也就是說经磅,加了__block修飾符的變量泌绣,就與之前的不痛了,該變量就變成了一個結(jié)構(gòu)體预厌,通過指向自己的指針再來改變對應(yīng)值阿迈。
5.Block存儲域( _NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock)
通過前面的學(xué)習(xí),了解到Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量,__block修飾符修飾的變量轉(zhuǎn)換為block變量的結(jié)構(gòu)體類型的自動變量轧叽,所謂結(jié)構(gòu)體類型的自動變量苗沧,即棧上生成的該結(jié)構(gòu)體的實例變量。:
名稱 | 實質(zhì) |
---|---|
Block | 棧上Block的結(jié)構(gòu)體實例 |
__block變量 | 棧上__block變量的結(jié)構(gòu)體實例 |
根據(jù)咱們之前提到的炭晒,其實Block也是一種對象待逞,并且Block的類是_NSConcreteStackBlock,和他類似的還有兩個如:
- _NSConcreteMallocBlock
- _NSConcreteGlobalBlock三個不同的類名稱決定了三個Block類生成的Block對象存在內(nèi)存中的位置:
| 類名 | 對象的存儲域 |
| :--------: |: --------:|
| _NSConcreteStackBlock | 棧上 |
|_NSConcreteMallocBlock|堆上|
|_NSConcreteGlobalBlock|程序的數(shù)據(jù)區(qū)域(.data區(qū))|
到目前為止,出現(xiàn)的Block例子都是_NSConcreteStackBlock類网严,所以都是設(shè)置在棧上识樱,但實際上并非是這樣,在記述全局變量的地方使用Block語法時生成的Block為_NSConcreteGlobalBlock對于Block對象分配在數(shù)據(jù)區(qū)的情況,略過分析過程震束,直接總結(jié):
- 當(dāng)把Block聲明為全局變量的時候怜庸,Block分配在數(shù)據(jù)區(qū):
void (^blk)(void) = ^{printf("Global Block\n");}; int main() {}
- Block語法表達(dá)式中不使用截獲的自動變量的時候:
typedef int (^blk_t)(int); for (int rate = 0; rate < 10; ++rate) { blk_t blk = ^(int count){return count;}; }
以上兩種情況,Block分配在數(shù)據(jù)區(qū)垢村。最后一個問題割疾,什么時候Block會分配在堆上呢?此時可以引出之前說的一個問題嘉栓,“Block可以超出變量作用域而存在”宏榕,換句話說就是,Block倘若作為一個局部變量存在侵佃,結(jié)果他居然可以在超出作用域之后不被廢棄,同樣的,由于__block修飾的變量也是放在棧上的担扑,如果其變量作用域結(jié)束,那么__block修飾符修飾的變量也應(yīng)該結(jié)束趣钱。解決方案如下:
將Block和__block修飾的變量從棧上復(fù)制到堆上來解決,將配置在棧上的Block復(fù)制到堆上胚宦,這樣即使Block語法記述的變量作用域結(jié)束時首有,堆上的Block還可以繼續(xù)存在
復(fù)制到堆上之后,將Block內(nèi)部的isa成員變量進行改變:
impl.isa = &_NSConcreteMallocBlock;
當(dāng)ARC有效時枢劝,大多數(shù)情況下編譯器會進行恰當(dāng)?shù)剡M行判斷井联,自動生成將棧上復(fù)制到堆上的代碼,并且最后復(fù)制到堆上的Block會自動的加入到autoRealeasePool中您旁,編譯器不能進行判斷的情況便是:
-
向方法或函數(shù)的參數(shù)中傳遞Block時
但是在向方法或函數(shù)的參數(shù)中傳遞Block時也有不需要手動復(fù)制的情況如下:
- Cocoa框架的方法且方法名中含有usingBlock等時
- GCD中的API
舉個栗子:在使用NSArray類的enumeratObjectsUsingBlock實例方法以及dispatch_async函數(shù)時烙常,不用手動復(fù)制,相反的,在NSArray類的initWithObjects實例方法上傳遞時需要手動復(fù)制蚕脏,看代碼:
- (id) getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk0:%d", val);}, ^{NSLog(@"blk1:%d", val);}, nil]; }
接下來侦副,調(diào)用:
`id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();` 結(jié)果就是Block在執(zhí)行時發(fā)生異常,應(yīng)用程序強制結(jié)束驼鞭,這是由于在getBlockArray函數(shù)執(zhí)行結(jié)束時秦驯,棧上的Block被廢棄的緣故。而此時編譯器恰好又不能判斷是否需要復(fù)制挣棕。 注:但將Block從棧上復(fù)制到堆上是相當(dāng)消耗CPU的译隘,當(dāng)Block設(shè)置在棧上也能夠使用時,將Block從棧上復(fù)制到堆上只是在浪費CPU資源洛心,能少復(fù)制固耘,盡量少復(fù)制。
將以上代碼修改一下即可運行:
- (id) getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk0:%d", val);} copy], [^{NSLog(@"blk1:%d", val);} copy], nil]; }
小結(jié):
Block的類 | 副本源的配置存儲域 | 復(fù)制效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteGlobalBlock | 程序數(shù)據(jù)區(qū)域 | 什么都不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加 |
6. __block變量存儲域(Block移動對__block變量的影響)
使用__block變量的Block從棧復(fù)制到堆上時词身,__block修飾的變量也會受到影響厅目。
__block變量的配置存儲域 | Block從棧復(fù)制到堆時的影響 |
---|---|
棧 | 從棧復(fù)制到堆并被Block持有 |
堆 | 被Block持有 |
- 1.多個Block使用一個__block變量時,因為會將所有的Block配置在棧上偿枕,所以__block變量也會配置在棧上璧瞬,其中任何一個Block從棧復(fù)制到堆時,__block變量也會一并從棧復(fù)制到堆并被該Block持有渐夸,當(dāng)剩下的Block從棧復(fù)制到堆時嗤锉,被復(fù)制的Block會依次持有__block變量,并增加__block變量的引用計數(shù)墓塌。
- 2.在這里瘟忱,讀者可以采用Objective-C的引用計數(shù)的方式來考慮。使用block變量的Block持有__block變量苫幢,如果Block被廢棄访诱,它所持有的__block變量也就被釋放在這里,回到之前講到的“__block變量使用結(jié)構(gòu)體成員變量__forwarding的原因”韩肝,不管__block變量配置在棧上還是在堆上触菜,都能夠正確的訪問該變量(通過指針),通過Block的復(fù)制哀峻,__block變量也從棧復(fù)制到堆涡相,此時可同時訪問棧上的__block變量和堆上的__block變量,下面解釋一下:看代碼:
__block int val = 0; void (^blk)(void) = [^{++val;} copy]; ++val; blk(); NSLog(@"%d", val);
結(jié)果是:2
^{++val;}和++val;都可以轉(zhuǎn)化為++(val.__forwarding->val);
在變換Block語法的函數(shù)中剩蟀,該變量val為復(fù)制到堆上的__block變量結(jié)構(gòu)體實例催蝗,而另外一個(++val)與Block無關(guān)的變量val,為復(fù)制前棧上的__block變量結(jié)構(gòu)體實例育特。但是棧上的__block變量結(jié)構(gòu)體實例在__block變量從棧復(fù)制到堆上時丙号,會將成員變量__forwarding指針替換為復(fù)制目標(biāo)堆上的__block變量用結(jié)構(gòu)體實例的地址,如圖:
下面總結(jié)棧上的Block復(fù)制到堆的情況:
- 調(diào)用Block的copy實例方法時
- 將Block作為函數(shù)返回值時
- 將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞Block時
在調(diào)用Block的copy方法時,如果Block配置在棧上犬缨,那么該Block會從棧上賦值到堆喳魏;將Block作為函數(shù)返回值時、將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時遍尺,編譯器將自動地將對象的Block作為參數(shù)并調(diào)用_Block_copy函數(shù)截酷,這與調(diào)用Block的copy實例方法的效果相同;在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞Block時乾戏,在該方法或函數(shù)內(nèi)部對傳遞過來的Block調(diào)用Block的copy實例方法或者_(dá)Block_copy函數(shù)迂苛。