Block原理分析(2)完結(jié)

前情提要

基于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)體的成員變量fmtcapturedVariable是在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,autoregister說(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)為_NSConcreteStackBlockOC有很多與之類(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的帆焕,以及GCDAPI。其他情況下要記得加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ì)將所有的Block配置在棧上琳骡,所以__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)體中增加的成員變量copydispose,以及作為指針賦值給該成員變量的__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)用Blockcopy方法;

- Block作為函數(shù)返回值時(shí);

- 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或是Block類(lèi)型成員變量時(shí);

- 在方法名中含有usingBlockCocoa框架方法或GCDAPI中傳遞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阐虚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末序臂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子实束,更是在濱河造成了極大的恐慌奥秆,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咸灿,死亡現(xiàn)場(chǎng)離奇詭異构订,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)避矢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)悼瘾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人审胸,你說(shuō)我怎么就攤上這事亥宿。” “怎么了歹嘹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵箩绍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我尺上,道長(zhǎng)材蛛,這世上最難降的妖魔是什么圆到? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮卑吭,結(jié)果婚禮上芽淡,老公的妹妹穿的比我還像新娘。我一直安慰自己豆赏,他們只是感情好挣菲,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著掷邦,像睡著了一般白胀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抚岗,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天或杠,我揣著相機(jī)與錄音,去河邊找鬼宣蔚。 笑死向抢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胚委。 我是一名探鬼主播挟鸠,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼亩冬!你這毒婦竟也來(lái)了艘希?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鉴未,失蹤者是張志新(化名)和其女友劉穎枢冤,沒(méi)想到半個(gè)月后鸠姨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铜秆,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年讶迁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了连茧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巍糯,死狀恐怖啸驯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祟峦,我是刑警寧澤罚斗,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站宅楞,受9級(jí)特大地震影響针姿,放射性物質(zhì)發(fā)生泄漏袱吆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一距淫、第九天 我趴在偏房一處隱蔽的房頂上張望绞绒。 院中可真熱鬧,春花似錦榕暇、人聲如沸蓬衡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狰晚。三九已至,卻和暖如春缴啡,著一層夾襖步出監(jiān)牢的瞬間家肯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工盟猖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讨衣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓式镐,卻偏偏與公主長(zhǎng)得像反镇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娘汞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359