Blocks實現原理講解

全乎的Blocks講解

一句話概括Blocks:<strong>帶有自動變量(局部變量)的匿名函數</strong>

Blocks模式

Block語法

Block的書寫形式:<strong>^ 返回類型 參數列表 表達式</strong>

^int (int count) {  
        return count + 1;
    } 

我們可以看到Block的語法和C語言函數相比有兩點不同

  1. 沒有函數名
  2. 帶有“^”
    省略形式:
    1、返回類型。省略返回類型時,如果表達式中有return語句就使用該返回類型,如果表達式沒有return語句就是用void類型。表達式中含有多個return語句時,所有return的返回類型必須相同。
    2、參數列表。如果使用參數,參數列表也可以忽略。
    綜上,最簡單的Block形式如下:
    ^{
        printf("Blocks\n");
    }

Blocks變量

(一)Block類型變量與一般的C語言變量完全相同,可作為一下用途使用:
1、自動變量
2、函數參數
3、靜態變量
4、靜態全局變量
5、全局變量
當在函數和返回值中使用Block類型變量是,記述方式極為復雜。此時,我們可以像使用函數指針類型那樣,使用typedef來解決問題。

    typedef int (^blk_t)(int);
    void func(int (^blk)(int))//原來的記述方式
    void func(blk_t blk)  //現在的記述方式
      ·
    int (^func())//原來的記述方式
    blk_t func() //現在的記述方式

(二)Block中變量值得獲取
Block表達式截獲所使用的自動變量的值,即保存自動變量的瞬間值。
(三)__block說明符
默認情況下,執行Block語法時,截獲的是自動變量的瞬間值,截獲保存之后就不能改寫這個值,如果嘗試修改這個值,編譯器會報編譯錯誤。
如果想在Block語法的表達式中將值賦給Block語法外聲明的自動變量,修改在該自動變量上附加__block說明符。

Blocks底層的實現

Block是“帶有自動變量的匿名函數”,實際上Block是作為極普通的c語言源代碼來處理的。我們先來看一下個最簡單的block語法:

    int main()
    {
        void (^blk)(void) = ^{printf("Block\n");};
        blk();
        return 0;
    }

我們使用clang將objc代碼轉換為c++的源代碼。說是c++,其實也僅是使用了struct結構,本質還是c代碼。

//block變量結構體
 struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
}
...
struct __main_block_impl_0 {
  struct __block_impl impl;
  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 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,          &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
}

objc中短短的幾行代碼,轉換為c++代碼后,竟然增加了這么多。我們從轉換后的代碼中選取出最重要的這幾行代碼。下面我們就來分析一下轉換的代碼,看看block到底是怎么通過c++代碼來實現的。
很簡單的,第一眼我們就看到轉換后的代碼中有一行printf("block\n");,沒錯,這就是objc代碼中的block塊內容。發現原來的Block函數,轉換成了一個c++函數:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("block\n");
    }

函數的名字是根據Block語法所屬的函數名和該Block語法在該函數出現的順序值來給Block函數命名。
此外,我們發現轉換的后函數有一個入參__cself,這個參數就相當于c++實例方法中指向實例自身的變量this或者objc實例方法中指向對象自身的變量self,即參數__cself為指向Block值得變量。我們先來看看這個參數的聲明:

struct __main_block_impl_0 * __cself

__cself__main_block_impl_0結構體的指針,該結構聲明如下:

//去除構造函數 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

__main_block_impl_0結構體有兩個成員變量,一個__block_impl 結構體impl,一個 __main_block_desc_0結構體指針Desc。一個一個的來看,
先看一下 __block_impl 這個結構體

 struct __block_impl {
        void *isa;  //如同對象類型的Class isa,將Block作為Objc對象是,關于該對象類的信息放置于isa指向的對象中
        int Flags;  //某些標志
        int Reserved; //保留區域
        void *FuncPtr; //函數指針
}

我們來分一下這個結構體中的四個參數:
1、isa,我們首先要認識到Blocks在objc也是對象,在Objective-C中使用id這一類型來存儲對象。

typedef struct objc_object {
    Class isa;
} *id;

objc_object是表示一個類的實例的結構體,id為objc_object結構體的指針類型。
該結構體只有一個字段,即指向 其類的isa指針。這樣,當我們向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對象的selector指向的方法。找到后即運行這個方法。當創建一個特定類的實例對象時,分配的內存包含一個objc_object數據結構,然后是類的實例變量的數據。
我們再來看看Class:

typedef struct objc_class *Class;

Class為objc_class結構體的指針類型。objc_class結構體的定義如下:

struct objc_class {
    Class isa; //在Objective-c中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,指向metaClass(元類)
    ·
    ·
    Class super_class; //指向該類的父類,如果該類已經是最頂層的根類,則super_class為NULL
}

在這里我們順便提一下元類的概念。上面介紹objc_class結構是,提到,所有的類自身也是一個對象,我們可以向這個對象發送消息。如:

NSArray *array = [NSArray array];

在這個例子中,+array消息發送給了NSArray類,而這個NAArray類也是一個對象。既然是對象,那么他也是一個Objc_object指針,他包含一個指向其類的一個isa指針。那么這些就有一個問題了,這個isa指針指向什么呢?為了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念:<strong>meta-class是一個類對象的類</strong>.
當我們向一個對象發消息時,runtime會在這個對象所屬的這個類的方法列表中查找相應方法,而向一個類發送消息是,會在這個類的meta-calss元類的方法列表中查找。
ps:meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同
實例-類-元類的關系如下圖:


</br>
回到正文,__main_block_impl_0結構就相當于objc_object結構體,其內部的isa指針目前一般初始化為isa = &_NSConcreterStackBlock;,即該Block類的信息放在了_NSConcreterStackBlock中。
2、Flags 標志位,默認設為0
3、Reserved 保留區
4、FuncPtr 函數指針,將Block轉換后的函數指針賦值給FuncPtr,供以后調用。

接下來我們來看一下這個__main_block_desc_0結構體,其聲明如下:

static struct __main_block_desc_0 {
  size_t reserved;  //保留區域
  size_t Block_size; //Block的大小
};

說了這么多,我們來看一下objc是怎么把Block轉換成c++函數調用的。
首先我們構造過程:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,          &__main_block_desc_0_DATA));

乍一看,這個函數比較復雜,轉換較多,我們分開來看:

struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,           &__main_block_desc_0_DATA);
struct __mian_block_impl_0 *blk = &tmp;

整個過程就是將__mian_block_impl_0結構體類型的自我變量,即棧上生成的__mian_block_impl_0結構體實例的指針,賦值給__mian_block_impl_0結構體指針類型的變量blk。對應最初源代碼:

void (^blk)(void) = ^{printf("Block\n");};

下面就來看看__mian_block_impl_0結構體實例的構造函數:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)

第一個參數是由Block語法轉換后的C語言函數指針,第二個參數是作為靜態全局變量初始化的__main_block_desc_0結構體實例指針。__main_block_desc_0的初始化如下:

static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
        0, 
        sizeof(struct __main_block_impl_0) //__main_block_impl_0結構體實例的大小
    };

__mian_block_impl_0結構體實例具體是如何初始化的呢,我們將__mian_block_impl_0結構體中的成員變量展開,如下:

struct __mian_block_impl_0 {
    void *isa; 
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0 * Desc;
}

初始化如下:

isa = &_NSConcreterStackBlock; 
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

再來看一下調用過程:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

去掉轉換部分:

(*blk->FuncPtr)(blk)

就是簡單地使用函數指針調用函數。Block語法轉換的__main_block_func_0函數指針被賦值給成員變量FuncPtr。此外,我們也發現了__main_block_func_0函數的參數__cself指向Block值。

Block截獲自動變量值

上一節我們講解了Block語法的底層實現,這一節主要講解如何截取自動變量的值,前面曾提到過,Block獲取的是自動變量的瞬間值。和前面一樣,我們還使用clang對源代碼進行轉換。
源代碼如下:

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt,val);};
    blk();
    return 0;
}

轉換后的代碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  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->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt,val);}

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()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

與上一節的代碼作比較,我們注意到,在__main_block_impl_0結構體中增加兩個變量const char *fmtint val,類型與自動變量的類型完全相同。而且,還發現Block語法表達式中沒有使用的自動變量不會被追加。即Blocks的自動變量截獲只針對Block中使用的自動變量。
接下來我們再看一下構造函數:

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val);

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

可以發現,執行Block語法時,使用自動變量fmt和val來初始化__main_block_impl_0結構體實例。
再來看一下執行函數:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
    printf(fmt,val);
}

再轉換后的函數中,使用的是自動變量保存在__main_block_impl_0結構體實例中的值。由此也說明了,Block獲取的是自動變量的瞬間值。

__block說明符

前面我們說到,Block獲取變量時是保存自動變量的瞬間值,在Block內部如果想要改變自動變量的值,編譯器會報編譯錯誤。那么如何才能在Block內部實現修改保存自動變量的值呢,在沒用使用__block說明符之前,我們可以根據C語言的特性來實現:
1、靜態變量
2、靜態全局變量
3、全局變量
我們來看看這段源代碼:

int global_val = 1;
static int static_global_val = 2;
int main()
{
    static int static_val = 3;
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    return 0;
}

改源代碼里面使用Block改寫了靜態變量static_val、靜態全局變量static_global_val和全局變量global_val。轉換后的代碼如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
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; //靜態變量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));  //靜態變量static_val的指針傳遞給__main_block_impl_0結構體的構造函數
    return 0;
}

我們看到,對于靜態全局變量static_global_val和全局變量global_val,在轉換后的函數中直接使用。但是對于靜態變量static_val卻進行了轉換,使用靜態變量static_val的指針對其進行訪問。將靜態變量static_val的指針傳遞給__main_block_impl_0結構體的構造函數并保存。這里我們就要問了,為什么自動變量不保存它的指針呢。
在實際使用中,自動變量分配在棧上,在由Block語法生成的Block上,經常情況下Block會在超過其變量作用域的時刻執行,當變量作用域結束時,自動變量就廢棄了,通過自動變量的指針去訪問已廢棄的自動變量,會發生錯誤。
</br>
Objective-C提供了 __block說明符來解決這個問題。 __block說明符用來指定Block中想變更的自動變量。且看下面的源代碼:

int main()
{
    __block int val = 3;  //__block修改val自動變量
    void (^blk)(void) = ^{
        val *= 1;   //修改自動變量的值
    };
    return 0;
}

轉換后的代碼如下:

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), 3};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}

可以看到,僅僅是增加了一個 __block 說明符,源代碼就急劇增加。
__block 變量val變為了一個結構體實例

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

該結構體的初始化過程如下:

__Block_byref_val_0 val = {
    0, //__isa
    &val, //__forwarding指向自己
    0,  //__flags = 0
    sizeof(__Block_byref_val_0), //__size 為自身__Block_byref_val_0的大小
    10,  //自動變量的值
}

那么如何給__block變量賦值呢?

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

通過__main_block_impl_0結構體中__Block_byref_val_0結構體實例的指針找到自動變量的結構體實例,然后通過自動變量結構體的成員變量__forwarding__forwarding持有指向該實例自身的指針)訪問成員變量val(原變量).


我們為什么是通過成員變量__forwarding而不是直接去訪問結構體中我們需要修改的變量呢? __forwarding被設計出來的原因又是什么呢?

Block存儲域

我們知道,Block語法塊轉換為Block結構體類型(函數)的自動變量,__block變量轉換為__block變量的結構體類型的自動變量。而這兩種結構體類型的自動變量,存在于棧上。
通過前面的介紹,我們知道Block也是個Objective-C對象,將Block當作Objective-C對象來看待時,該Block的類為_NSConcreteStackBlock,之前我們提過Block類中有一個isa指針,在初始化時指向_NSConcreteStackBlock,其實除了這個之外還有兩個類似的類:

  • __NSConcreteStackBlock
  • __NSConcreteGlobalBlock
  • __NSConcreteMallocBlock

通過名字我們就可以知道__NSConcreteStackBlock表示類的對象Block設置在棧上。
__NSConcreteGlobalBlock 表示類的對象Block設置在程序的數據區(.data)中
__NSConcreteMallocBlock 表示類的對象Block設置由malloc函數分配的內存塊(堆)中
但是到目前為止,Block使用的都是__NSConcreteStackBlock 類,都是設置在棧上。但是也不全是這樣,在記述全局變量的地方使用Block語法時,生成的Block為__NSConcreteGlobalBlock。此外,如果Block不截獲自動變量,就可以將Block用結構體實例設置在程序的數據區(使用__NSConcreteGlobalBlock類)。
那么什么時候使用__NSConcreteMallocBlock這個類呢。這里我們就來順便解決一下上面遺留的問題:

  • Block超出變量作用域可存在的原因
  • __block變量用結構體成員變量__forwarding存在的原因。

我們知道,當一個變量設置在全局區時,從變量的作用于外也可以通過指針安全的使用。但是當設置在棧上時,當作用域結束時,這個Block就被廢棄了。___block變量也是同樣的情況。
Blocks提供了將Block和__block 變量從棧上復制到堆上的方法來解決這個問題。將配置在棧上的Block復制到堆上,這樣即使Block語法記述的變量作用域結束,堆上的Block還可以繼續存在。 當Block從棧上復制到堆上時,Block將結構體實例的isa設置成__NSConcreteMallocBlock
我們再來看看__forwarding成員變量,我們知道,有時候__block變量配置在堆上,也可以訪問棧上的__block變量。在這種情況下,只要棧上的結構體實例成員變量__forwarding指向堆上的結構體實例,那么不管是從棧上的__block變量還是從堆上的__block變量都能正確的訪問。
那么什么時候棧上的Block會復制到堆上呢?

  • 調用Block的copy實例方法
  • Block作為函數返回值返回時
  • 將Block賦值為附有__strong修飾符id類型的類或Block類型成員變量時
  • 在方法名中含有usingBlock的Cocoa框架方法或者Grand Central Dispatch的API中傳遞Block時。

__block變量存儲域

上一節介紹了Block的存儲,那么__block變量又是如何處理的呢,當Block從棧復制到堆時,使用的所有__block變量也全部被從棧復制到堆。此時,Block持有__block變量。
在多個Block中使用__block變量時,因為最先會將所有的Block配置在棧上,所以__block變量也會配置在棧上。在任何一個Block從棧復制到堆時,__block變量也會一并從棧復制到堆并被該Block所持有。當剩下的Block從棧復制到堆時,被復制的Block持有__block變量,并增加__block變量的引用計數。
如果配置在堆上的Block被廢棄,那么它所使用的__block變量也就被廢棄。
之前我們提到過一句話:“不管__block變量配置在棧上還是在堆上,都能夠訪問該變量”。這也就是__forwarding成員變量存在的原因。
這里引用一下上面出現過的代碼:

 (val->__forwarding->val) *= 1;

在使用__block變量時,通過__forwarding成員變量訪問val成員(原變量)。當__block變量從棧上復制到堆上時,會將__forwarding成員變量的值替換為復制目標堆上的__block變量用結構體實例的地址。
ps: 棧上和堆上同時保持__block變量實例,但是訪問和修改值則是在堆上。看張圖吧:

截獲Block對象

我們看一下源代碼:

blk_t blk;
int main()
{
    id array = [[NSMutableArray alloc] init];
    blk = [^(id obj) {
        [array addObject:obj];
    } copy];
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
}

代碼中使用copy函數將Block從棧上復制到堆上,而使用的array這個自動變量也一并復制到堆上。(當ARC有效是,id類型以及對象類型變量必定附加所有權標識符,缺省為附有__strong 修飾符的變量)。Objective-C的運行時庫能夠準確把握Block從棧上復制到堆上以及Block被廢棄的時機,能夠在恰當的時機進行初始化和廢棄。為此需要使用在__main_block_desc_0結構體中增加的成員變量copy和dispose,即作為指針賦值給該成員變量的__main_block_copy_0__main_block_dispose_0 函數。我們來依次看看這兩個函數的具體實現:

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

_Block_object_assign 函數調用,相當于retain實例方法的函數。將對象賦值在對象類型的結構體成員變量中。有retain方法,肯定有release方法。__main_block_dispose_0函數調用__main_block_dispose_0函數釋放賦值在Block中的結構體成員變量arrar中的對象。

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

__main_block_dispose_0 函數相當于release實例方法函數,釋放賦值在對象類型的結構體成員變量中的對象。
有了這種構造,通過使用附有__strong 修飾符的自動變量,Block中截獲的對象就能夠超出其變量作用域而存在。
我們回頭看看,其實這兩個函數在使用 __block 變量時已經出現過了:

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

和Block結構體的部分基本相同,不同支出在于__main_block_copy_0__main_block_dispose_0函數最后的參數不一樣。BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF區分對象類型是對象還是__block變量。
由此可知。Block中使用的賦值為附有__strong修飾符的自動變量的對象和復制到堆上的__block變量由于被堆上的Block所持有,因而可超出其變量作用域而存在。

Block循環引用

如果在Block中使用附有__strong修飾符的自動變量,那么當Block從棧復制到堆上時,該對象為Block所持有,很容易引起循環引用。這種情況經常出現在一個類型對象中,有一個Block類型的成員變量,而這個Block中又引用了self變量,這就會造成一個循環引用。Block持有self,self持有Block。

- (id) init {
    self = [super init];
    blk = ^{NSLog(@"self = %@",self);
    return self;
}

若由于Block引發了循環引用,根據Block的用途選擇使用__block變量、__weak修飾符或__unsafe_unretained修飾符來避免循環引用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容