block的深度探究(截取自動(dòng)變量柜与、__block、截獲對(duì)象谈为、存儲(chǔ)域、copy函數(shù) 與 dispose函數(shù)踢关、循環(huán)引用伞鲫、ARC無效時(shí)Block的copy/release)

本章目錄

  • Block截取自動(dòng)變量
  • __block說明符
  • Block存儲(chǔ)域
  • __block變量存儲(chǔ)域
  • Block中截獲對(duì)象
  • Block的copy函數(shù) 與 dispose函數(shù)調(diào)用時(shí)機(jī)
  • __block變量和對(duì)象
  • Block循環(huán)引用
  • ARC無效時(shí)Block的copy / release
  • 參考文獻(xiàn)

Block截取自動(dòng)變量

  • 如何截獲自動(dòng)變量?

Block語法轉(zhuǎn)換成C函數(shù)后签舞,Block語法表達(dá)式中用到的自動(dòng)變量會(huì)被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中秕脓。
而此結(jié)構(gòu)體中的成員變量類型與自動(dòng)變量類型完全相同柒瓣,但僅限于Block語法中使用到的自動(dòng)變量。
因此吠架,所謂“截獲自動(dòng)變量值”意味著執(zhí)行Block語法時(shí)芙贫,Block語法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例中。

  • 為什么Block語法中不能使用數(shù)組(C語言創(chuàng)建的數(shù)組)傍药?

因?yàn)榻Y(jié)構(gòu)體中的成員變量與自動(dòng)變量類型完全相同磺平。
所以結(jié)構(gòu)體中使用數(shù)組截取數(shù)組值,而后調(diào)用時(shí)再賦值給另一個(gè)數(shù)組拐辽。
也就是數(shù)組賦值給數(shù)組拣挪,這在C語言中是不被允許的。

簡(jiǎn)單來說:Block轉(zhuǎn)化后俱诸,是個(gè)C語言的Struct結(jié)構(gòu)體菠劝。在C語言里,數(shù)組是不能直接賦值給數(shù)組的睁搭。所以赶诊,Block中無法截獲C語言中的數(shù)組,因?yàn)閷?duì)于C語言數(shù)組而言园骆,直接賦值會(huì)報(bào)錯(cuò)舔痪。

而我們平常對(duì)NSMutableArray的增刪和遍歷操作是可以用的,但是遍歷時(shí)不可進(jìn)行刪操作遇伞,否則數(shù)組越界報(bào)錯(cuò)辙喂。

__block說明符

  • 在Block中修改截獲的自動(dòng)變量值有兩種方法。

  • 第一種:使用靜態(tài)變量鸠珠,靜態(tài)全局變量巍耗,全局變量

從Block語法轉(zhuǎn)換成的C語言函數(shù)中訪問靜態(tài)全局變量 / 全局變量并沒有任何改變,可直接使用渐排。
但是靜態(tài)變量卻是用靜態(tài)變量的指針來對(duì)其進(jìn)行訪問炬太,這是超出作用域使用變量的最簡(jiǎn)單方法。

  • 為何靜態(tài)變量的這種方法不適用于自動(dòng)變量?

因?yàn)殪o態(tài)變量會(huì)存儲(chǔ)在堆上驯耻,而自動(dòng)變量卻存在棧上亲族。
當(dāng)超出其作用域的時(shí)候,靜態(tài)變量還會(huì)存在可缚,而自動(dòng)變量所占內(nèi)存則會(huì)被釋放因而被廢棄霎迫。所以不能通過指針訪問原來的自動(dòng)變量。

  • 第二種方法: 使用 “ __block ” 修飾符

__block存儲(chǔ)域類說明符帘靡。存儲(chǔ)域類說明符知给,指定將變量設(shè)置到哪個(gè)存儲(chǔ)域中,如:auto 棧 , static 堆 , extern 全局 , register 寄存器 ,

若將__block修飾符修飾在一個(gè)自動(dòng)變量前,如:

int main() {
    __block int val = 10;
    void (^blk)(void) = ^{val = 1;};
}

通過clang編譯后提取的Block相關(guān)代碼為:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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));

}

在此源碼中涩赢,__block(前面有兩個(gè)“ _ ”戈次,這個(gè)markDown編譯器為何把這兩個(gè)符號(hào)給吞了 )所修飾的變量也同Block一樣變成了__Block_byref_val_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例筒扒。
關(guān)于此結(jié)構(gòu)體的聲明:

struct __Block_byref_val_0 {
  void *__isa; (isa指針怯邪,指向一個(gè)存儲(chǔ)有自身基礎(chǔ)信息的地址,類似OC中的元類)
__Block_byref_val_0 *__forwarding; (forwarding指針花墩,指向自身)
 int __flags; (標(biāo)記)
 int __size; (大小)
 int val; (存儲(chǔ)的值悬秉,相當(dāng)于原自動(dòng)變量的成員變量)
};

給__block變量賦值的代碼 ^{val = 1;}; 會(huì)被轉(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變量賦值時(shí)(又吞了)搂捧,Block的__main_block_impl_0結(jié)構(gòu)體實(shí)例會(huì)持有指向__block變量的_Block_byref_val_0 結(jié)構(gòu)體實(shí)例的指針。

也就是Block的__main_block_impl_0結(jié)構(gòu)體實(shí)例會(huì)持有一個(gè)指針懂缕,指向__block修飾的變量所轉(zhuǎn)換的__Block_byref_val_0結(jié)構(gòu)體實(shí)例允跑。

而__Block_byref_val_0結(jié)構(gòu)體通過成員變量__forwarding指針訪問成員變量val

另外,為了能在多個(gè)Block中使用__block變量搪柑,__block變量的__Block_byref_val_0結(jié)構(gòu)體并不在Block用__main_block_impl_0結(jié)構(gòu)體中聋丝。
Block是通過__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針來訪問。

Block存儲(chǔ)域

Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動(dòng)變量工碾,__block(又吞了)變量轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型的自動(dòng)變量弱睦。結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例渊额。

所以况木,Block的實(shí)質(zhì)是棧上Block的結(jié)構(gòu)體實(shí)例,__block變量(又吞了)的實(shí)質(zhì)是 棧上__block變量的結(jié)構(gòu)體實(shí)例。

Block有三種類型:
_NSConcreteStackBlock
_NSConcreteGlobalBlock
_NSConcreteMallocBlock


Block的類型與Block的存儲(chǔ)區(qū)域有關(guān):
_NSConcreteStackBlock類的Block對(duì)象設(shè)置在棧上(Stack 棧)
_NSConcreteGlobalBlock類的Block對(duì)象與全局變量一樣旬迹,設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中(global 全局)
_NSConcreteMallocBlock類的Block對(duì)象則設(shè)置在堆中(Malloc 函數(shù)分配的內(nèi)存塊)

在聲明全局變量的地方使用Block語法火惊,生成的Block為_NSConcreteGlobalBlock
則該Block中的isa指針初始化時(shí): impl.isa = &_NSConcreteGlobalBlock;
代表該Block的類為_NSConcreteGlobalBlock類。
則該Block結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域中奔垦。

還有一種情況屹耐,Block為_NSConcreteGlobalBlock類對(duì)象:
Block語法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)。(注意是自動(dòng)變量椿猎,包括函數(shù)形參和非static局部變量惶岭,說明這個(gè)變量是在棧上運(yùn)行時(shí)自動(dòng)創(chuàng)建自動(dòng)撤銷。)

因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣?dòng)變量犯眠,所以不存在對(duì)自動(dòng)變量進(jìn)行截獲按灶。
由此Block用結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴于執(zhí)行時(shí)的狀態(tài),所以整個(gè)程序中只需一個(gè)實(shí)例筐咧。
因此將Block用結(jié)構(gòu)體實(shí)例設(shè)置在與全局變量相同的數(shù)據(jù)區(qū)域中即可鸯旁。

也就是說只有當(dāng)截獲自動(dòng)變量時(shí),Block結(jié)構(gòu)體實(shí)例截獲的值才會(huì)根據(jù)執(zhí)行時(shí)的狀態(tài)變化。
而當(dāng)Block語法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)羡亩,Block結(jié)構(gòu)體實(shí)例截獲的值不會(huì)根據(jù)執(zhí)行時(shí)的狀態(tài)變化。

列如:

typedef int (^blk_t)(int);
for(int rate = 0; rate < 10; rate++){
    blk_t blk = ^(int count){return rate * count;};
}

此Block結(jié)構(gòu)體實(shí)例每次for循環(huán)中截獲的值都不同危融,但去掉return rate * count 中的自動(dòng)變量rate畏铆,則Block的結(jié)構(gòu)體實(shí)例每次截獲的值都完全相同。

所以:

記述全局變量的地方有Block語法時(shí)
Block語法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)(函數(shù)形參和非static的局部變量)
在以上兩種情況下吉殃,Block為_NSConcreteGlobalBlock類對(duì)象辞居。即Block配置在程序的數(shù)據(jù)區(qū)域中。

那么蛋勺,若Block使用了一個(gè)全局變量Int a瓦灶,而 a 在每次使用前都會(huì)修改值。
如:

typedef int (^blk_t)(int);
extern int a ;
for(int rate = 0; rate < 10; rate++){
    a = rate;
    blk_t blk = ^(int count){return count;};
    blk(a);
}

則該Block的類型仍舊為_NSConcreteGlobalBlock抱完,因?yàn)椴lock語法的表達(dá)式中沒有使用應(yīng)截獲的自動(dòng)變量贼陶,是直接使用的全局變量。

除此之外的Block語法生成的Block為_NSConcreteStackBlock類對(duì)象巧娱,且設(shè)置在棧上碉怔。
而棧上的Block通過Copy操作改變到堆上,就是_NSConcreteMallocBlock類對(duì)象禁添。

設(shè)置在棧上的Block撮胧,如果其所屬的變量作用域結(jié)束,該Block就會(huì)被廢棄老翘。

若__block變量也配置在棧上芹啥,如果其所屬的變量作用域結(jié)束,則該__block(又吞)變量也會(huì)被廢棄铺峭。
因此墓怀,Block通過Copy操作會(huì)復(fù)制到堆上,其所引用的__block(又吞)變量也會(huì)復(fù)制到堆上逛薇。

復(fù)制到堆上的Block其所屬類型為:impl.isa = &_NSConcreteMallocBlock;

當(dāng)__block變量復(fù)制到堆上時(shí)捺疼,之前所說的__block(又吞,下同永罚,不再贅述)變量的結(jié)構(gòu)體成員變量__forwading會(huì)指向復(fù)制到堆上的自身啤呼,由此實(shí)現(xiàn)無論__block變量配置在堆上還是棧上都能夠正確的訪問__block變量。

那么編譯器何時(shí)會(huì)對(duì)Block自動(dòng)判斷進(jìn)行Copy(復(fù)制)操作何時(shí)不能呢袱?

在ARC環(huán)境下自動(dòng)判斷進(jìn)行Copy操作:

  • 將Block作為函數(shù)參數(shù)返回值返回時(shí)官扣,編譯器會(huì)自動(dòng)進(jìn)行Copy操作。
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時(shí)

如果不能自動(dòng)Copy羞福,調(diào)用一下Copy實(shí)例方法就可以復(fù)制到堆上惕蹄。

ARC環(huán)境下,返回一個(gè)對(duì)象時(shí)會(huì)先將該對(duì)象賦值給一個(gè)臨時(shí)實(shí)例指針,而后對(duì)其進(jìn)行retain操作卖陵,最后將對(duì)象注冊(cè)到注冊(cè)表中返回遭顶。
而runtime/objc-arr.mm里面有說明,Block的retain操作objc_retainBlock函數(shù)實(shí)際上是Block_copy函數(shù)泪蔫。
在執(zhí)行對(duì)Block的retain操作棒旗,objc_retainBlock函數(shù)執(zhí)行后,棧上的Block復(fù)制到堆上撩荣,同時(shí)返回堆上的地址作為指針賦值給臨時(shí)實(shí)例變量铣揉。

  • 編譯期不能自動(dòng)判斷進(jìn)行Copy操作:

  • 向方法或函數(shù)的參數(shù)中傳遞Block時(shí)。(注意:Cocoa框架中的方法餐曹,其方法名中若含有usingBlock等時(shí) 與 GCD的API中會(huì)自動(dòng)判斷進(jìn)行Copy操作)逛拱。

如NSArray的initWithObjects需要手動(dòng)執(zhí)行Copy操作,而enumerateObjectsUsingBlock則不用台猴,因?yàn)榫幾g器會(huì)自動(dòng)進(jìn)行復(fù)制操作朽合。
復(fù)制操作,就是Block的實(shí)例執(zhí)行Copy實(shí)例方法即可饱狂。

  • Block的復(fù)制效果

注意: 無論Block配置在何處旁舰,用copy方法復(fù)制都不會(huì)引起任何問題!包括內(nèi)存泄露也不可能嗡官!不用擔(dān)心無法進(jìn)行releas操作箭窜!所以不確定的時(shí)候盡情調(diào)用Copy方法吧!

如:blk = [[[[blk copy] copy] copy] copy];
可轉(zhuǎn)換為:

{
    //第一次copy操作會(huì)將配置在棧上的Block賦值給變量blk中

    //而后對(duì)blk進(jìn)行Copy后衍腥,將配置在堆上的Block賦值給變量tmp
    //此時(shí)變量tmp持有強(qiáng)引用的Block

    blk_t tmp = [blk copy];

    //將變量tmp的Block賦值為變量blk磺樱,變量blk持有強(qiáng)引用的Block
    //因?yàn)橹百x值的Block配置在棧上,所以不受此賦值的影響婆咸。
    //此時(shí)Block的持有者為變量blk 和 變量 tmp

    blk = tmp;
}
    //變量作用域結(jié)束竹捉,所以變量tmp被廢棄,其強(qiáng)引用失效并釋放所持有的Block
    //由于Block被變量blk持有尚骄,所以沒有被廢棄

{
    //配置在堆上的Block被賦值變量blk,同時(shí)變量blk持有強(qiáng)引用的Block    

    //配置在堆上的Block被賦值到變量tmp中块差,變量tmp持有強(qiáng)引用的Block
    blk_t tmp = [blk copy];

    //由于向變量blk進(jìn)行了賦值,所以現(xiàn)在賦值的Block的強(qiáng)引用失效倔丈,Block被釋放
    //由于Block被變量tmp所持有憨闰,所以未被廢棄
    //變量blk中賦值了變量tmp的Block,變量blk持有強(qiáng)引用的Block
    //此時(shí)Block的持有者為變量tmp與變量blk
    blk = tmp;
}
    //變量作用域結(jié)束需五,變量tmp被廢棄
    //強(qiáng)引用失效并釋放所持有的Block
    //由于變量blk還處于持有的狀態(tài)鹉动,Block沒有被廢棄

    //以下重復(fù),先用一個(gè)變量tmp持有堆上的Block再賦值給blk
    //于是blk釋放舊值,保留新值
    //超出變量作用域后釋放tmp宏邮,此時(shí)只有blk持有
{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}

__block變量存儲(chǔ)域

block 1

如圖所示泽示,一個(gè)Block從棧復(fù)制到堆時(shí)缸血,使用的所有__block變量也都會(huì)復(fù)制到堆上并被Block持有。若此時(shí)__block變量已經(jīng)在堆上械筛,則被該Block持有捎泻。

若配置在堆上的Block被廢棄,那么它所有的__block變量也就被釋放埋哟。

在代碼:

__block int val = 0;
 void (^blk)(void) = [^{++val;} copy];
++val;
blk();

利用copy方法復(fù)制使用了__block變量的Block語法族扰,于是二者都從棧復(fù)制到堆上。

而其中 ^{++val;} 與 ++val; 都可轉(zhuǎn)換形式為: ++(val.__forwarding -> val);

在變換Block語法的函數(shù)中定欧,該變量val為復(fù)制到堆上的__block變量的結(jié)構(gòu)體實(shí)例。

而與Block無關(guān)的變量val怒竿,則為復(fù)制前棧上的__block變量用結(jié)構(gòu)體實(shí)例砍鸠。

但是棧上__block變量的結(jié)構(gòu)體實(shí)例在__block變量從棧復(fù)制到堆上時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制到目標(biāo)堆上的__block變量用結(jié)構(gòu)體實(shí)例的地址耕驰。如圖:

屏幕快照 2016-11-15 上午9.48.49.png

由此實(shí)現(xiàn)無論Block語法中爷辱、語法外使用__block變量,還是__block變量配置在堆上或棧上朦肘,都可以順利訪問同一個(gè)__block變量饭弓。

Block中截獲對(duì)象

在代碼:

   id array = [NSMutableArray array];
        blk_t blk = ^(id obj){
            
            [array addObject:obj];
            NSLog(@"array count = %ld",[array count]);
        
        };
        
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);

輸出結(jié)果為:
array count = 1 
array count = 2
array count = 3

意味著賦值給變量array的對(duì)象在超出其變量作用域后仍存在。
將源碼用clang編譯后媒抠,截取Block相關(guān)的部分:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
// @implementation block


struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
  __block__init_block_impl_0(void *fp, struct __block__init_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __block__init_block_func_0(struct __block__init_block_impl_0 *__cself, id obj) {
  id array = __cself->array; // bound by copy


            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_2587xc0n2dv6f08yv3mdbf7c0000gn_T_block_679c7b_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));

        }
static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __block__init_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __block__init_block_impl_0*, struct __block__init_block_impl_0*);
  void (*dispose)(struct __block__init_block_impl_0*);
} __block__init_block_desc_0_DATA = { 0, sizeof(struct __block__init_block_impl_0), __block__init_block_copy_0, __block__init_block_dispose_0};

//使用部分

 id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
 
 blk_t blk = ((void (*)(id))&__block__init_block_impl_0((void *)__block__init_block_func_0, &__block__init_block_desc_0_DATA, array, 570425344));

      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

轉(zhuǎn)換一下:
 id array = [NSMutableArray array];
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,array,570425344);

(*blk -> impl.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);b

請(qǐng)注意此結(jié)構(gòu)體:

struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
}

XCode8中對(duì)截獲的變量array 不進(jìn)行顯示__strong(這里也有兩個(gè) “ _ ")修飾了弟断,但依舊使用__block__init_block_copy_0 函數(shù)(在此之前是__main_block_copy_0) 和 __block__init_block_dispose_0函數(shù)(在此之前是__main_block_dispose_0)進(jìn)行管理。

在此之前的OC中趴生,C語言結(jié)構(gòu)體不能含有附有__strong修飾符的變量阀趴,因?yàn)榫幾g器不知道何時(shí)進(jìn)行C語言結(jié)構(gòu)體的初始化和廢棄操作,因此不能很好地管理內(nèi)存苍匆。
但是OC的運(yùn)行庫能準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上Block被廢棄的時(shí)機(jī)刘急。

因此Block的結(jié)構(gòu)體中可以恰當(dāng)?shù)剡M(jìn)行初始化和廢棄,附有__strong修飾符或__weak修飾符的變量浸踩。
為此需使用在__main_block_desc_0結(jié)構(gòu)體中增加的成員變量copy 和 dispose叔汁,以及作為指針賦值給該成員變量的__main_block_copy_0函數(shù)和__block__init_block_dispose_0函數(shù)。
但現(xiàn)在沒有成員變量copy 和 dispose 而是直接使用函數(shù)指針__block__init_block_copy_0 函數(shù) __block__init_block_dispose_0函數(shù).

由于Block結(jié)構(gòu)體中需要管理賦值給變量array的對(duì)象检碗,因此__block(這里有四個(gè) “ _ ")__init_block_copy_0函數(shù)通過_Block_object_assign函數(shù)將對(duì)象類型對(duì)象賦值給Block結(jié)構(gòu)體的成員變量array中并持有該對(duì)象据块。

static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_assign函數(shù)調(diào)用相當(dāng)于retain實(shí)例方法的函數(shù)。

__block(又尼瑪吞了四個(gè)“ _ ”折剃!)__init_block_dispose_0 函數(shù)使用_Block_object_dispose函數(shù)瑰钮,釋放賦值在Block用結(jié)構(gòu)體成員變量array中的對(duì)象。

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_dispose函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù)微驶,釋放賦值在對(duì)象類型的結(jié)構(gòu)體成員變量中的對(duì)象浪谴。

Block的copy函數(shù) 與 dispose函數(shù)調(diào)用時(shí)機(jī)

  • 什么時(shí)候棧上的Block會(huì)復(fù)制到堆上开睡?

1.調(diào)用Block的copy實(shí)例方法
2.將Block作為函數(shù)參數(shù)返回值返回時(shí),編譯器會(huì)自動(dòng)進(jìn)行Copy操作苟耻。
3.將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量
4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時(shí)

Block從棧復(fù)制到堆時(shí)_Block_copy函數(shù)被調(diào)用篇恒。
釋放復(fù)制到堆上的Block,誰都不持有Block而使其被廢棄時(shí)調(diào)用dispose函數(shù)凶杖。相當(dāng)于對(duì)象的dealloc實(shí)例方法胁艰。

由于此種構(gòu)造,Block中截獲的對(duì)象就能夠超出其變量作用域而存在智蝠。

另外腾么,在使用__block變量時(shí)也是使用了copy函數(shù)和dispose函數(shù)來管理。

通過這兩個(gè)參數(shù)來區(qū)分copy函數(shù)和dispose函數(shù)的對(duì)象類型對(duì)象還是__block變量杈湾。

copy函數(shù)持有截獲的對(duì)象 或 持有所使用的__block變量解虱,dispose函數(shù)釋放截獲的對(duì)象 或 釋放所使用的__block變量。

Block中使用的賦值給id類型的自動(dòng)變量的對(duì)象 和 復(fù)制到堆上的__block變量由于被堆上的Block所持有漆撞,因而可超出其變量作用域而存在殴泰。

需要注意的是,若Block不復(fù)制到堆上浮驳,則其不會(huì)持有截獲的對(duì)象悍汛。則對(duì)象會(huì)隨著變量作用域結(jié)束而結(jié)束。

__block變量和對(duì)象

如前文所述至会,在Block中使用附有__strong修飾符的id類型或?qū)ο箢愋妥詣?dòng)變量的情況下离咐,當(dāng)Block從棧復(fù)制到堆時(shí),使用_Block_object_assign函數(shù)持有Block截獲的對(duì)象奉件。

當(dāng)堆上的Block被廢棄時(shí)健霹,使用_Block_object_dispose函數(shù)釋放Block截獲的對(duì)象。

  • 在__block變量為附有__strong修飾符的id類型或?qū)ο箢愋妥詣?dòng)變量的情形下會(huì)發(fā)生同樣的過程

當(dāng)__block變量從棧復(fù)制到堆時(shí)瓶蚂,使用_Block_object_assign函數(shù)糖埋,持有賦值給__block變量的對(duì)象。
當(dāng)堆上的__block變量被廢棄時(shí)窃这,使用_Block_object_dispose函數(shù)瞳别,釋放賦值給__block變量的對(duì)象。

如:  
 __block id array = [NSMutableArray array];
        blk_t blk = ^{
            NSLog(@"%@",array);
        };
        blk();
會(huì)轉(zhuǎn)換為:
struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

__block變量聲明部分:
 __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
簡(jiǎn)化為: __Block_byref_array_0 array = {
    0,
    &array,
    33554432,
    sizeof(__Block_byref_array_0),
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131,
    [[NSMutableArray alloc]init]
};

在Block中使用附有__weak修飾符的id類型變量會(huì)如何杭攻?如以下代碼

   blk_t blk;
        
       {
            
            id array = [NSMutableArray array];
            id __weak array2 = array;
            blk = ^(id obj){
                
                [array2 addObject:obj];
                NSLog(@"array2 count = %ld" ,[array2 count]);
            };
        }
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
      
輸出結(jié)果為:
array2 count = 0
array2 count = 0
array2 count = 0

因?yàn)開_strong修飾的變量array在該變量作用域結(jié)束的同時(shí)被釋放祟敛、廢棄,nil被賦值給變量array2兆解。

即使在array2前面加上__block說明符馆铁,結(jié)果也是不變。因?yàn)閍rray依舊會(huì)被釋放廢棄锅睛,依舊是nil賦值給array2

另外埠巨,使用___unsafe_unretained修飾符的變量只不過與指針相同历谍,不會(huì)像__strong或__weak修飾符那樣進(jìn)行處理,因而通過懸垂指針訪問已被廢棄的對(duì)象程序會(huì)崩潰辣垒。
比如上述代碼的__weak替換為__unsafe_unretained就會(huì)崩潰望侈。

若一個(gè)變量obj同時(shí)指定__autoreleasing修飾符和__block說明符會(huì)引起編譯錯(cuò)誤,因?yàn)椴]有設(shè)定__autoreleasing修飾符與Block同時(shí)使用的方法勋桶。

Block循環(huán)引用

因?yàn)閷懙娜藢?shí)在太多脱衙,所以不想過多講述,簡(jiǎn)單提下吧例驹。

如果在Block中使用附有__strong修飾符的對(duì)象類型自動(dòng)變量捐韩,那么Block從棧復(fù)制到堆時(shí),該對(duì)象為Block所持有鹃锈。因而容易引起循環(huán)引用荤胁。

為避免此類事件,可用__weak修飾對(duì)象仪召。

而在面向iOS4 與 OS Snow Leopard的應(yīng)用程序中,必須使用__unsafe_unretained修飾符代替__weak松蒜。因?yàn)檫@個(gè)版本還沒有__weak扔茅。

另外還可使用__block變量來避免循環(huán)引用,只要在Block語法中手動(dòng)讓__block修飾的對(duì)象置為nil即可秸苗,因此只要最少調(diào)用一次Block即可避免召娜。

ARC無效時(shí)的copy / release

ARC無效時(shí),需要手動(dòng)將Block從棧復(fù)制到堆惊楼。此外玖瘸,ARC無效時(shí)要手動(dòng)釋放復(fù)制的Block。使用copy實(shí)例方法來復(fù)制檀咙,用releas實(shí)例方法來釋放雅倒。

只要Block有一次復(fù)制并配置在堆上,就可通過retain實(shí)例方法持有弧可。若配置在棧上則retain實(shí)例方法不起任何作用蔑匣。因此推薦用copy方法來持有。

由于Block是C語言的擴(kuò)展棕诵,所以在C語言中也可以使用Block語法裁良。此時(shí)用Block_copy函數(shù) 和 Block_release函數(shù)代替Copy / release實(shí)例方法,使用方法和引用計(jì)數(shù)的思考方式同OC中的copy / release實(shí)例方法:
Block_copy(blk)校套、Block_release(blk)
Block_copy函數(shù)就是之前提過的_Block_copy函數(shù)

ARC無效時(shí)价脾,當(dāng)Block從棧復(fù)制到堆時(shí),若Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣?dòng)變量笛匙,不會(huì)被retain侨把。因此,ARC無效時(shí)__block說明符被用來Block中的循環(huán)引用犀变。而若Block使用的變量為沒有__block說明符的id類型或?qū)ο箢愋偷淖詣?dòng)變量,則無論ARC有效與否都會(huì)被retain座硕。

參考文獻(xiàn):

Objective - C 高級(jí)編程:iOS與OS X多線程和內(nèi)存管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弛作,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子华匾,更是在濱河造成了極大的恐慌映琳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜘拉,死亡現(xiàn)場(chǎng)離奇詭異萨西,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旭旭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門谎脯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人持寄,你說我怎么就攤上這事源梭。” “怎么了稍味?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵废麻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我模庐,道長(zhǎng)烛愧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任掂碱,我火速辦了婚禮怜姿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疼燥。我一直安慰自己沧卢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布醉者。 她就那樣靜靜地躺著搏恤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪湃交。 梳的紋絲不亂的頭發(fā)上熟空,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音搞莺,去河邊找鬼息罗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛才沧,可吹牛的內(nèi)容都是我干的迈喉。 我是一名探鬼主播绍刮,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼挨摸!你這毒婦竟也來了孩革?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤得运,失蹤者是張志新(化名)和其女友劉穎膝蜈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熔掺,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饱搏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了置逻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片推沸。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖券坞,靈堂內(nèi)的尸體忽然破棺而出万牺,到底是詐尸還是另有隱情腔寡,我是刑警寧澤浸策,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布粟誓,位于F島的核電站噪沙,受9級(jí)特大地震影響芥映,放射性物質(zhì)發(fā)生泄漏往扔。R本人自食惡果不足惜缎讼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一菌瘫、第九天 我趴在偏房一處隱蔽的房頂上張望蜗顽。 院中可真熱鬧,春花似錦雨让、人聲如沸雇盖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔挖。三九已至,卻和暖如春庵寞,著一層夾襖步出監(jiān)牢的瞬間狸相,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工捐川, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脓鹃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓古沥,卻偏偏與公主長(zhǎng)得像瘸右,于是被迫代替她去往敵國(guó)和親娇跟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容