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

前情提要

基于Block原理分析(1),繼續(xù)分析Block中的剩余知識點(diǎn)。

1.__block 說明符

我們再來回顧前面截獲自動變量的例子。
^{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中所使用的的被截獲自動變量就如"帶有自動變量值的匿名函數(shù)"所說,僅截獲自動變量的值。Block中使用自動變量后,在Block的結(jié)構(gòu)體實(shí)例中重寫該自動變量也不會改變原來截獲的自動變量。詳細(xì)解釋就是截獲的自動變量為__cself->fmt。而這個__cself就是blk,而blk結(jié)構(gòu)體的成員變量fmtcapturedVariable是在Block初始化過程中,以值傳遞的方式賦值給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自己。


如果我們試圖在Block內(nèi)部改變被Block截獲的值會怎么樣呢?系統(tǒng)會產(chǎn)生編譯錯誤。因?yàn)樵趯?shí)現(xiàn)上不能改寫被截獲自動變量值的值,所以當(dāng)編譯器在編譯過程中檢查出給被截獲自動變量值賦值的操作時,便產(chǎn)生了編譯錯誤。這樣一來就無法在Block中保存值了,極為不便。解決這個問題有兩種方法。

第一種:C語言中有三個變量,允許Block直接改寫其值。具體如下:
  • 靜態(tài)變量: 在函數(shù)內(nèi)定義,相比較于自動變量(生存期為函數(shù)的生存期),它的生存期為整個程序。但作用域與自動變量相同;
  • 靜態(tài)全局變量: 作用域?yàn)槎x它的文件內(nèi),生命周期為定義它文件的生命周期,大部分情況下同源程序;
  • 全局變量: 作用域?yàn)槿抗こ涛募?生命周期為整個源程序的生命周期;
    雖然Block語法的匿名函數(shù)部分(__main_block_func_0)變換為了C語言函數(shù),但從這個變換的函數(shù)中訪問靜態(tài)全局變量,全局變量并沒有任何改變,可直接使用。但是靜態(tài)變量的情況下,轉(zhuǎn)換后的函數(shù)原本就設(shè)置在含有Block語法的函數(shù)外(靜態(tà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;
}

這個結(jié)果我們已經(jīng)很熟悉了,對靜態(tài)全局變量static_global_val和全局變量global_val的訪問與轉(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;
    }

那這個__cself->static_val又是誰呢?

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ù)&static_val。進(jìn)行了一個指針傳遞,這是超出作用域使用變量的最簡單方法??梢宰约河?code>printf打印再確認(rèn)一下。實(shí)際上,在由Block語法生成的值Block上,可以存有超過其變量作用域的被截獲對象的自動變量。變量作用域結(jié)束的同時,原來的自動變量被廢棄,因此Block中超過變量作用域而存在的變量同靜態(tài)變量一樣,將不能通過指針訪問原來的自動變量。但該變量在Block內(nèi)仍然可以訪問,這種情況發(fā)生在將Block從棧復(fù)制到堆上,與此同時__block修飾的變量也會一同被復(fù)制到堆上, Block持有該對象。

第二種:使用"__block說明符"。更準(zhǔn)確的表述是:"__block存儲類域說明符"(這個類不是面向?qū)ο蟮念悾穷愑?。C語言中有以下存儲類域說明符:typedef,extern,static,auto,register。

__block說明符類似于static,autoregister說明符,他們用于指定將變量值設(shè)置到哪個存儲域中,例如,auto表示作為自動變量存儲在棧中,static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)內(nèi)。
下面我們來實(shí)際使用__block說明符,用它來指定Block中想變更值的自動變量。
.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;
}

加一個__block相較于之前轉(zhuǎn)換的代碼出現(xiàn)了很多新面孔,我們一個一個地看。
__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};

整體簡化一下:

 __Block_byref_val_0 val = {
        (void*)0,
        (__Block_byref_val_0 *)&val,
         0,
         sizeof(__Block_byref_val_0),
         10
};

我們發(fā)現(xiàn),這個自動變量變成了一個結(jié)構(gòu)體實(shí)例。__block修飾的變量(val)也同Block一樣變成__Block_byref_val_0結(jié)構(gòu)體類型的自動變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例,該變量初始化為10,并且這個值(10)也出現(xiàn)在了結(jié)構(gòu)體實(shí)例的初始化中,這意味著該結(jié)構(gòu)體持有相當(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)變量賦值時,使用了指向該靜態(tài)變量的指針,而向__block變量賦值要比這個更為復(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)。通過成員變量__forwarding訪問成員變量val。(成員變量int val是該實(shí)例自身持有的變量,它相當(dāng)于原自動變量val)。
究竟為什么會有成員變量__forwarding呢?是為了保證,無論這個被捕獲的變量是在棧上還是堆上都能正確地訪問這個變量(唯一性)。
另外,__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中這樣做是為了在多個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(拿的地址),而這個val是上面創(chuàng)建的__Block_byref_val_0結(jié)構(gòu)體實(shí)例
兩個Block都使用了__Block_byref_val_0結(jié)構(gòu)體實(shí)例val的地址(&val),這樣就可以在多個Block中使用同一個__block變量了。當(dāng)然,反過來一個Block也可以使用多個__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));

下面我們來了解Block超出變量作用域仍可存在的理由,以及__block變量的結(jié)構(gòu)體成員變量__forwarding存在的理由。討論之前,我們先來看一下Block的存儲域。

  • 通過前面說明可知,Block(^{printf("This is a Block")};)轉(zhuǎn)換為Block的結(jié)構(gòu)體類型(__main_block_impl_0)的自動變量,__block變量(__block int val = 20;)轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型(__Block_byref_val_0)的自動變量。所謂的結(jié)構(gòu)體類型的自動變量,就是棧上生成的該結(jié)構(gòu)體的實(shí)例。另外,通過之前的說明可知Block也是OC對象。將Block當(dāng)做OC對象來看時,該Block的類為_NSConcreteStackBlockOC有很多與之類似的類,如:_NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock
  • 首先我們能夠注意到_NSConcreteStackBlock中含有stack一詞,即該類的對象Block設(shè)置在棧上。同樣地,_NSConcreteMallocBlock類對象設(shè)置在由mallo函數(shù)分配的內(nèi)存塊即堆中,而_NSConcreteGlobalBlock類對象則設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中。
  • 到現(xiàn)在為止出現(xiàn)的Block例子使用的都是_NSConcreteStackBlock類,且都設(shè)置在棧上,但實(shí)際上并非全是這樣,在記述全局變量的地方使用Block語法時,生成的Block_NSConcreteGlobalBlock類對象,例如:void (^blk)(void) = ^{printf("Global Block\n")};轉(zhuǎn)換后的impl.isa = &_NSConcreteGlobalBlock;,該Block的類為_NSConcreteGlobalBlock類。此Block設(shè)置在程序的數(shù)據(jù)區(qū)域內(nèi)。因?yàn)樵谑褂萌肿兞康牡胤讲淮嬖谧詣幼兞?,所以不存在對自動變量進(jìn)行截獲一說。也就是說Block用結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序中只需一個實(shí)例。因此,只要不需要用Block來截獲自動變量,就可以將Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域內(nèi)。

那么在Block配置在堆上的_NSConcreteMallocBlock類對象是在何時使用的呢?這正是上一節(jié)最后遺留問題的答案。遺留問題為:Block超出變量作用域可存在的原因,和__block變量用結(jié)構(gòu)體成員變量__forwarding存在的原因。

  • 配置在全局變量上的Block,從變量作用域外也可以通過指針安全地使用。但設(shè)置在棧上的Block。如果其所屬的變量作用域結(jié)束,該Block就被廢棄。由于__block變量也配置在棧上,同樣地,如果其所屬的變量作用域結(jié)束。則該__block變量也會被廢棄。
  • Blocks提供了將__block變量從棧上復(fù)制到堆上的方法來解決這個問題。將配置在棧上的Block復(fù)制到堆上,這樣即使Block語法記述的變量作用域結(jié)束,堆上的Block還可以繼續(xù)存在。
  • 復(fù)制到堆上的Block_NSConcreteMallocBlock類對象寫入Block用結(jié)構(gòu)體實(shí)例的成員變量isaimpl.isa = &_NSConcreteMallocBlock;
  • __block變量用結(jié)構(gòu)體成員變量__forwarding可以實(shí)現(xiàn)無論__block變量配置在棧上還是堆上,都能夠正確地訪問__block修飾的變量。有時在__block變量配置在堆上的狀態(tài)下,也可以訪問棧上的__block變量。在此情形下,只要棧上的結(jié)構(gòu)體成員變量__forwarding指向堆上的結(jié)構(gòu)體實(shí)例,那么不管是從棧上的__block變量還是從堆上的__block變量都能正確訪問。
  • 那么Blocks提供的復(fù)制方法究竟是什么呢?實(shí)際上當(dāng)ARC有效時,大多數(shù)情形下編譯器會恰當(dāng)?shù)剡M(jìn)行判斷,自動生成Block從棧上復(fù)制到堆上的代碼。
    例:
typedef int (^blk_t)(int);
blk_t testFunc(int rate) {
    return ^(int count){return rate * count;};
}

非ARC下編譯是不能通過的,會提示:error: returning block that lives on the local stack return ^(int count){return rate * count;};
而ARC環(huán)境下的代碼會被編譯為:

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)行時庫可知,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);將通過Block語法生成的Block(&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate))賦值tmp。
2.接下來tmp = objc_retainBlock(tmp);相當(dāng)于tmp = __Block_copy(tmp);就是把棧上的tmp也就是原來生成的Block結(jié)構(gòu)體實(shí)例,復(fù)制到堆上,再把堆上的這個Block的地址賦值給tmp?,F(xiàn)在tmp所指的就是堆上的Block結(jié)構(gòu)體實(shí)例。
3.最后return objc_autoreleaseReturnValue(tmp);將堆上的結(jié)構(gòu)體實(shí)例作為對象,注冊到autoreleasepool中然后返回該對象。即,ARC下將Block作為函數(shù)返回值,編譯器會自動生成復(fù)制到堆上的代碼。


那什么情況下是編譯器不能恰當(dāng)判斷的呢?

就是向方法或函數(shù)的參數(shù)中傳遞Block時。但如果在方法或函數(shù)中適當(dāng)?shù)貜?fù)制了傳遞過來的參數(shù)(Block),那么就不必再調(diào)用該方法或函數(shù)手動復(fù)制了。比如Cocoa框架的方法且方法名中含有usingBlock的,以及GCDAPI。其他情況下要記得加copy,([XXBlock copy];)。另外對于已配置在堆上和數(shù)據(jù)區(qū)域的Block調(diào)用copy方法是,前者引用計(jì)數(shù)會加1,后者什么也不做。但無論Block配置在何處,用copy方法都不會引起任何問題。在不確定時,調(diào)用copy方法即可。在ARC下即便多次調(diào)用copy,編譯器也能正確地控制引用計(jì)數(shù),所以沒問題。


__block變量存儲域

若在一個Block中使用__block變量,則當(dāng)Block從棧上復(fù)制到堆上時。這些__block變量也全部被從棧復(fù)制到堆。此時,Block持有__block變量。即使在該Block已復(fù)制到堆上的情形下,復(fù)制Block也對所使用的__block變量沒有任何影響。在多個Block中使用__block變量時,因?yàn)樽钕葧⑺械?code>Block配置在棧上,所以__block變量也會配置在棧上。在任何一個Block從棧復(fù)制到堆時,__block變量也會一并從棧復(fù)制到堆并被該Block所持有。當(dāng)剩下的Block從棧復(fù)制到堆時,被復(fù)制的Block持有__block變量,并增加__block變量的引用計(jì)數(shù)。如果配置在堆上的Block被廢棄,那么它所使用的__block變量也就被釋放。

聊聊__forwarding的作用:不管__block變量配置在棧上還是在堆上,都能夠正確地訪問該變量??慈缦?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í)例的時候,__forwarding指向的是他自己(val棧);copy的時候,會把Block復(fù)制到堆上,同時也把__block結(jié)構(gòu)體實(shí)例復(fù)制到堆上,并且棧上的__block的成員變量__forwarding會指向堆上的自己(val堆)。執(zhí)行Block內(nèi)部的++(val(棧)->__forwarding->val(堆))。執(zhí)行外層val++:++(val(棧)->__forwarding->val(堆))。打印:val(棧).__forwarding->val(堆)。通過該功能,無論是在Block語法中,Block語法外使用__block變量,還是__block變量配置在棧上或者堆上,都可以順利訪問同一個__block變量。


截獲對象

.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)行時庫能夠準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時機(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ù)將對象類型對象賦值給Block用結(jié)構(gòu)體的成員變量testArray中并持有該對象。用來管理賦值給Block用結(jié)構(gòu)體中的testArray的對象。相當(dāng)于retain實(shí)例方法,將對象賦值在對象類型的結(jié)構(gòu)體成員變量中。另外_Block_object_dispose用來釋放testArray中的對象,相當(dāng)于release實(shí)例方法,釋放賦值在Block用結(jié)構(gòu)體成員變量testArray中的對象。在Block從棧上復(fù)制到堆時,以及堆上的Block被廢棄時才會調(diào)用這兩個函數(shù)。

Block復(fù)制到堆上的時機(jī):

- 調(diào)用Blockcopy方法;

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

- 將Block賦值給附有__strong修飾符id類型的類或是Block類型成員變量時;

- 在方法名中含有usingBlockCocoa框架方法或GCDAPI中傳遞Block時;

總結(jié)

_Block_copy函數(shù)被調(diào)用時Block被從棧上復(fù)制到堆上。通過這種方式,Block截獲的對象就能夠超出其變量作用域而存在。在Block中使用__block時,也會有這兩個方法,不同的是截獲對象是:BLOCK_FIELD_IS_OBJECT,截獲__block自動變量是:BLOCK_FIELD_IS_BYREF。通過這兩個參數(shù)區(qū)分對象類型還是__block變量。由此可知,Block中使用的賦值給附有__strong修飾符的自動變量的對象和復(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賦值給了對象的成員變量blk_,相當(dāng)于該Block自動調(diào)用了copy方法,Block被從棧復(fù)制到堆上,而且Block還截獲了__strong修飾符修飾的self對象,self一并被復(fù)制到堆上并被Block所持有。因此,對象持有blk_(Block)Block持有對象(MyObject),形成了循環(huán)引用,此時,不會調(diào)用對象的dealloc方法。為避免這種循環(huán)引用,id __weak tmp = self,之后Block中使用tmp就沒問題了。
另外也可以使用__block變量來避免循環(huán)引用,但需要在Block體中,給該變量賦值nil。并執(zhí)行改Block。如不執(zhí)行Block,將該變量賦值為nil,仍然會引發(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時可動態(tài)決定是否將nil或是其他對象賦值在__block變量中。缺點(diǎn)是,為避免循環(huán)引用必須要執(zhí)行Block。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容