Blocks的實現

前言

Blocks的原理,每當本身對知識體系有必定提高以後,再回過頭來看一下曾經讀過的書籍,會發現對它的理解逐步加深。藉着讀書筆記活動,立個小目標,把Block完全搞明白,重讀《Objective-C高級編程 iOS與OS X多線程和內存管理》第二章節block原理部分,一方面給本身作個筆記,另外一方面加深一下印象。編程

目錄

  • Block的實質
  • Block捕獲自動變量的值
  • __block的實質
  • Block存儲域
  • __block變量存儲域
  • 截獲對象
  • __block變量和對象
  • Block循環引用

1.block實質

block代碼:安全

void (^blk)(void) = ^ {
        printf("Block");
    };
    blk();
複製代碼

執行xcrun -sdk iphonesimulator clang -rewrite-objc 源代碼文件名就能將含有Block的代碼轉換爲C++的源代碼。我是按照書上的示例,一樣轉換的main.m文件,轉換完以後這裏就會多出一個main.cpp的文件,打開很恐怖,六萬多行... bash

實際上和block相關的代碼在最後幾十行:

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

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 argc, char * argv[]) {

    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;
}
複製代碼

這就是咱們一直在使用的block,由於都是struct結構看上去有點抽象,不過理解起來並不難。多線程

首先先從__main_block_func_0函數開始,由於咱們想要執行的回調看源碼都是寫在這個函數裏面的,block使用的匿名函數(也就是咱們定義的block)實際上被做爲簡單的C語言函數(block__main_block_func_0)來處理,該函數的參數__cself至關於OC實例方法中指向對象自身的變量self,即__self爲指向Block值的變量。__self與OC裏面的self相同也是一個結構體指針,是__main_block_impl_0結構體的指針,這個結構體聲明以下:框架

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;
  }
};
複製代碼

第一個變量是impl,也是一個結構體,聲明以下:iphone

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
複製代碼

先看FuncPrt,這個就是block括號中函數的函數指針,調用它就能執行block括號中的函數,實際上在調用block的時候就是調用的這個函數指針,執行它指向的具體函數實現。 第二個成員變量是Desc指針,如下爲其__main_block_desc_0結構體聲明:函數

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
複製代碼

其結構爲從此版本升級所需的區域和Block的大小。 實際上__main_block_impl_0結構體展開最後就是這樣:學習

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  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;
  }
複製代碼

這就是整個__main_block_impl_0結構體所包含的,既然定義了這個結構體的初始化函數,那在詳細看一下它的初始化過程,實際上該結構體會像下面這樣初始化:ui

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
複製代碼

__main_block_func_0這不就是上面說到的那個指向函數實現的那個函數指針,也就是說只須要調用到結構體裏面的FuncPtr就能調用到咱們的具體實現了。那這個構造函數在哪裏初始化的,看上面的源碼是在咱們定義block的時候:

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 __main_block_impl_0 *blk = &tmp;
複製代碼

該源代碼將__mian_block_impl_0結構體類型的自動變量,即棧上生成的__mian_block_impl_0結構體實例的指針,賦值給__mian_block_impl_0結構體指針類型的變量blk。聽起來有點繞,實際上就是咱們最開始定義的blk__main_block_impl_0結構體指針指向了__main_block_impl_0結構體的實例。

接下來看看__main_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結構體實例的大小。

接下來看看棧上的__main_block_impl_0結構體實例(即Block)是如何根據這些參數進行初始化的。也就是blk()的具體實現:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
複製代碼

簡化如下:

(*blk->impl.FuncPtr)(blk);
複製代碼

FuncPtr正是咱們初始化__main_block_desc_0結構體實例時候傳進去的函數指針,這裏使用這個函數指針調用了這個函數,正如咱們剛纔所說的,有block語法轉換的__main_block_func_0函數的指針被賦值成員變量FuncPtr中。blk也是做爲參數進行傳遞的,也就是最開始講到的__cself。到此block的初始化和調用過程就結束了。

2.Block捕獲自動變量的值

基於上面的例子,額外增長個局部變量val:

int val = 0;
 void (^blk)(void) = ^{
    NSLog(@"%d",val);
  };
        
 val = 10;
 blk();
        
複製代碼

轉換爲C++代碼以下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : 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) {
  int val = __cself->val; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,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 argc, char * argv[]) {
    
    int val = 0;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));

    val = 10;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

這與上一節轉換的代碼稍有差別,自動變量被做爲了成員變量追加到了__main_block_impl_0結構體中。在__main_block_impl_0結構體中聲明的成員變量類型與自動變量類型徹底相同(block語法表達式中,沒有使用的自動變量不會被追加,也就是若是變量沒有在block內被使用,是不會被捕獲到的)。

另外__main_block_impl_0結構體的構造函數與上一篇也有差別:

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
複製代碼

在初始化__main_block_impl_0結構體實例時,自動變量val被以參數的形式傳遞到告終構體裏面,就是在咱們定義block的時候,捕獲的自動變量會被用來初始化這個結構體:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
複製代碼

實際上帶有這種自動變量的block會像下面這樣初始化:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 0;
複製代碼

由此能夠看到,在__main_block_impl_0結構體被初始化的時候,變量val的值被捕獲到了而且賦值給了__main_block_impl_0結構體裏面的_val成員變量,實際上是值的捕獲,並不是內存地址,因此咱們在外部沒法修改。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val);
        }
複製代碼

__cself->val__cself上一篇已經講過了它指向的就是這個block對象__cself->val就是訪問的__main_block_impl_0的成員變量_val,而自動變量的值又賦給了_val,因此咱們在外部改變自動變量的值在block內部是不會生效的。

3.__block的實質

咱們若是想要修改block截獲的自動變量的值,靜態全局變量,全局變量和靜態變量,block內是不會捕獲到他們的的,因此這類變量在block內部,是能夠進行改寫值的。那麼他們具體在代碼層面上是怎麼作的仍是經過上面的命令看一下源碼:

int global_var = 1;
static int static_global_var = 2;

int main(int argc, char * argv[]) {
    static int static_var = 3;
    void (^blk)(void) = ^{
        global_var *= 1;
        static_global_var *= 2;
        static_var *= 3;
    };
    
    blk();
    
    return 0;
}
複製代碼

通過轉換後的源碼:

int global_var = 1;
static int static_global_var = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_var, int flags=0) : static_var(_static_var) {
    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_var = __cself->static_var; // bound by copy

        global_var *= 1;
        static_global_var *= 2;
        (*static_var) *= 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(int argc, char * argv[]) {
    static int static_var = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_var));

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

    return 0;
}
複製代碼

對靜態全局變量static_global_var和全局變量global_var的訪問與轉換前徹底相同, 那麼靜態局部變量是如何轉換的呢:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_var = __cself->static_var; // bound by copy

        global_var *= 1;
        static_global_var *= 2;
        (*static_var) *= 3;
    }
複製代碼

經過int *static_var = __cself->static_var可以看出,其實是將靜態局部變量static_var的指針傳遞給了__main_block_impl_0結構體,這也是超出變量做用域去使用變量的一種方法,那就是經過指針去訪問。那爲何在局部變量不這麼使用呢,這個後面再說,咱們如今只須要知道static修飾的局部變量是能夠在block內部進行值的改變的。迴歸主題,那麼自動變量咱們是怎麼去修改它的值的,就是經過__block進行修飾,看下代碼:

int main(int argc, char * argv[]) {
    __block int var = 10;
    void (^blk)(void) = ^{
        var = 1;
    };
    
    blk();
    
    return 0;
}

複製代碼

轉換後以下:

struct __Block_byref_var_0 {
  void *__isa;
__Block_byref_var_0 *__forwarding;
 int __flags;
 int __size;
 int var;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_var_0 *var; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__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_var_0 *var = __cself->var; // bound by ref

        (var->__forwarding->var) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 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(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));

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

    return 0;
}
複製代碼

不難發現多了一個__Block_byref_var_0結構體實例,它也正是__block的實現。該結構體中的成員變量var就至關於block外面的自動變量的成員變量。而後咱們再看一下block是怎麼給這個成員變量進行賦值操做的:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_var_0 *var = __cself->var; // bound by ref

        (var->__forwarding->var) = 1;
    }
複製代碼

剛剛在block中給靜態變量賦值的時候,使用了指向該靜態變量的指針,可是用__block修飾的時候,實際上可以看到__Block_byref_var_0結構體中也就是__block有一個成員變量__Block_byref_var_0 *__forwarding,是一個指向該實例自身的指針,經過成員變量__forwarding就能訪問到它自身的var,那麼究竟爲何要經過這個指向自身的__forwarding來訪問成員變量var下一節會說,咱們先知道它就是使用這種方式來訪問這個自動變量的。實際上咱們爲何能訪問到這個成員變量var,是由於在給自動變量定義爲__block類型的時候,就會初始化一個__Block_byref_var_0類型的結構體,而且默認將該變量初始化爲10(由於咱們給var初始化的10),至關於持有了原自動變量的成員變量。而後在初始化__main_block_impl_0結構體的時候就將這個block結構體做爲參數傳遞了過去,這樣__cself->var實際上就是咱們剛纔說的初始化的block的結構體,var->__forwarding->var就是訪問了這個block的結構體的__forwarding成員變量,__forwarding成員變量指向的又是自身,因此__forwarding->var返回的就是自身的成員變量var,這樣整個流程就走通了,具體爲何要有個_forwarding咱們繼續往下看。

4.Block存儲域

經過上面的分析,如今出現了幾個問題須要解釋:

1.爲何要有個_forwarding?(後面說)

2.BLock做用域在棧上,超出變量做用域是否是就銷燬了?

上面分析的block和__block都是結構體類型的自動變量,在棧上生成,稱爲「棧塊」,實際上還存在兩種block,「堆塊」「全局塊」。全局塊與全局變量同樣,設置在程序的.data數據區域,堆塊顧名思義,分配在堆上,類型分別以下:

  • _NSConcreteGlobalBlock
  • _NSConcreteStackBlock
  • _NSConcreteMallocBlock

有兩種狀況,是默認會分配在數據區域上的:

  • 1.記述全局變量的地方有block語法時。
  • 2.block語法的表達式中不使用截獲的自動變量的值。

除此以外的Block語法生成的Block爲設置在棧上的_NSConcreteStackBlock類對象。配置在全局變量上的Block從變量做用域外也能夠經過指針安全的使用,但設置在棧上的Block,若是所屬的變量做用域結束,該Block就被廢棄。因爲__block也配置在棧上,一樣的__block變量也會被廢棄。Blocks提供了將block和__block變量從棧上覆制到堆上的方法來解決這個問題。這樣即便block語法記述的變量做用域結束,堆上的block還能夠繼續存在(原文解釋)。大概意思就是有些狀況下,編譯器會默認對棧block生成一個copy到堆上的操做。大多數狀況下,編譯器會適當的進行判斷是否會將棧塊拷貝到堆上,有一種狀況除外:

  • 向方法或函數的參數中傳遞Block。

就是說block做爲參數傳遞的時候是須要咱們手動執行copy的,編譯器不會自動執行copy。儘管這樣,仍是有兩種狀況是不須要咱們手動實現,由於他們函數內部已經實現了copy操做:

  • Cocoa框架的方法切方法中含有usingBlock。
  • GCD的API。

舉個例子,把書上面的例子本身手動實現了一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    id object = [self getBlockArray];
    typedef void(^blk_t)(void);
    blk_t blk = (blk_t)[object objectAtIndex:0];
    blk();
}

- (id)getBlockArray {
    int var = 10;
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d",var);},
            ^{NSLog(@"blk1:%d",var);}, nil];
}
複製代碼

在執行blk()的時候程序異常崩潰了。由於getBlockArray函數執行結束的時候,在棧上建立的block被廢棄了,這個時候編譯器並無自動執行copy操做,須要咱們手動實現。爲何編譯器不對全部的棧塊都執行copy到堆上,書上明確說明了:block從棧複製到堆上是至關消耗CPU的,將block設置在棧上也可以使用時,將block複製到堆上只是在浪費CPU資源。因此這種狀況下對block執行copy就能夠了:

- (id)getBlockArray {
    int var = 10;
    return [[NSArray alloc] initWithObjects:
            [^{NSLog(@"blk0:%d",var);} copy],
            [^{NSLog(@"blk1:%d",var);} copy], nil];
}
複製代碼
2018-12-24 12:33:33.526163+0800 Blocks-捕獲變量的值[54592:3223484] blk0:10
複製代碼

文中還提到了若是屢次調用copy會不會有問題,答案固然是沒有問題,在ARC下是不用擔憂屢次copy引發內存問題。

還有一個_forwarding的問題沒有說,至少如今已經知道咱們設置在棧上的block由於執行了copy操做到了堆上,因此咱們無需關心它會超出做用域而被釋放的問題了,那麼_forwarding繼續往下看。

5.__block變量存儲域

若是在Block中使用了__block變量,那麼該Block從棧複製到堆時,所使用的__block也會被複制到堆上,而且會被Block持有。每個使用了當前__block變量的Block被複制到堆上時都會對這個__block引用計數+1,若是配置在堆上的Block被廢棄,相應的它所使用的__block引用計數會-1,直到全部的Block被釋放那麼此__block也會隨之釋放。

也就是說,除了上面說到的兩種狀況,咱們其他的Block基本都會複製到堆上,也就是說咱們使用的__block也會相應的跟着複製到堆上,像OC對象同樣,擁有引用計數。那麼咱們再分析一下以前遺留的問題,_forwarding是幹嗎的,當__block被複制到堆上的時候,棧上面的__block結構體裏面的_forwarding成員變量就會指向堆裏面的__block結構體實例,此時堆上面的__block變量的_forwarding會指向本身自己。也就以下圖這個樣子:

回顧一下上面__block的實質舉過的例子,咱們在用__block修飾自動變量的時候,在func函數裏面修改此變量值的時候,經過(var->__forwarding->var) = 1;這種方式去改變的,var->__forwarding實際上訪問的是堆上的__block結構體,var->__forwarding->var就是堆裏面結構體的var成員變量。這樣就算是棧上面的__block被釋放了,咱們還能夠去訪問堆裏面的var,這也是爲何自動變量不像static靜態變量那樣經過指針去訪問了,由於自動變量在做用域結束以後就會被釋放了,拷貝到堆上,做用域結束堆上面還會有其相應拷貝,這份拷貝只有在使用了它的Block釋放以後纔會釋放。

6.截獲對象

前面分析了__block修飾的自動變量超出做用域也能使用的原理,實際上對於對象類型,Block對其捕獲以後在處理上和__block很像,那麼具體使用__block對變量捕獲以後當Block和__block被拷貝到堆上和他們被釋放這兩個過程具體作了什麼以前也沒有詳細講到,經過捕獲對象的學習,也能夠對前面作個總結和思考。直接看下書上面的示例代碼:

typedef void(^blk_t)(id);
    blk_t blk;
    
    {
        id array = [[NSMutableArray alloc] init];
        blk = [^(id obj){
            [array addObject:obj];
            NSLog(@"array count : %ld",[array count]);
        } copy];
    }
    
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
複製代碼
2018-12-25 12:25:06.678625+0800 Blocks-捕獲變量的值[56349:3341197] array count : 1
2018-12-25 12:25:06.679199+0800 Blocks-捕獲變量的值[56349:3341197] array count : 2
2018-12-25 12:25:06.679210+0800 Blocks-捕獲變量的值[56349:3341197] array count : 3
複製代碼

按理來講array在超出變量做用域的時候會被廢棄,可是根據打印結果來看一切正常。也就是說array在超出變量做用域後依然存在。經過轉換的源碼以下:

Block結構體部分:

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

            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_1dc794_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
        
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*/);
}

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

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部分:

typedef void(*blk_t)(id);
    blk_t blk;

    {
        id array = ((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))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
    }

    ((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")));
複製代碼

這一塊我本身測試了一下暫時有點疑問。由於按照書上的示例來看,array前是有__strong修飾的,可是從我轉換的源碼來看並未看到__strong修飾符。是不是說若是沒有使用weak修飾默認爲strong?我先默認這個id array是被__strong修飾的。文中講到,C語言結構體不能附有__strong修飾符的變量,由於編譯器不知道應什麼時候進行C語言結構體的初始化和廢棄操做,不能很好的管理內存。可是它能很好的把握Block從棧複製到堆和把Block從堆上廢棄的時機,所以就算Block結構體中含有OC修飾符的變量也同樣可以跟隨者Block的廢棄而廢棄。

由於Block結構體中含有__strong修飾符的對象,因此須要對它進行管理,和以前的Block源碼對比,在struct __main_block_desc_0結構體中多了兩個函數指針:

  • void (copy)(struct __main_block_impl_0, struct __main_block_impl_0*);
  • void (dispose)(struct __main_block_impl_0);

這其實在分析__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->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼

_Block_object_assign函數至關於調用了retain函數,將對象賦值在對象類型的結構體成員變量中。

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

_Block_object_dispose函數至關於調用了release實例方法的函數,釋放賦值在對象類型的結構體成員變量中的對象。可是從轉換的源碼來看,__main_block_copy_0__main_block_dispose_0函數指針都沒有被調用,那麼它們是在何時觸發的?

  • 棧上的Block複製到堆時 --> 觸發copy函數。
  • 堆上的Block被廢棄時 -->觸發dispose函數。

Block被廢棄上面說了就是沒有對象強引用它就會被回收了,就會調用dispose方法。那麼何時棧上的Block會複製到堆上呢?

  • 1.調用Block的copy實例方法。
  • 2.Block做爲函數返回值返回時。
  • 3.將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時。
  • 4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時。

這樣經過__strong修飾符修飾的自動變量,就可以在做用域外使用了。

在前面使用__block的時候實際上這兩個函數就已經用到了,略微有點不一樣之處:

  • 截獲對象時 --> BLOCK_FIELD_IS_OBJECT
  • __block變量時 --> BLOCK_FIELD_IS_BYREF

經過這兩個參數用來區分是Block捕獲的是對象類型仍是__block變量。除此以外他們在copy和dispose時都是同樣的,都是被Block持有和釋放。

7.__block變量和對象

對於Block截獲__block修飾的變量仍是直接截獲對象的處理過程,上面都已經分析完了,包括它們關於內存的處理也都清晰了,惟獨使用__block修飾id類型的自動變量尚未說,實際上__block說明符能夠指定任何類型的自動變量,固然包括對象類型。仍是按照書上面的例子看下代碼:

__block id obj = [[NSObject alloc] init];
複製代碼

等同與

__block id __strong obj = [[NSObject alloc] init];
複製代碼

經過clang轉換以下:

/* __block結構體部分*/
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製代碼
/*__block變量聲明部分*/
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
        (void*)0,(__Block_byref_obj_0 *)&obj,
        33554432,
        sizeof(__Block_byref_obj_0),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
    };
複製代碼

這裏出現了上一節講到的_Block_object_assign_Block_object_dispose函數。實際上編譯器默認這個obj爲__strong類型,當Block從棧複製到堆上時,使用_Block_object_assign函數持有Block截獲的對象,當堆上的Block被廢棄時,使用_Block_object_dispose函數釋放Block截獲的對象。這說明使用__block修飾的__strong類型的對象,當__block變量從棧複製到堆上而且在堆上繼續存在,那麼該對象就會繼續處於被持有狀態。這與Block中使用賦值給附有__strong修飾符的對象類型自動變量的對象相同。

那麼除了__strong,若是用__weak修飾呢?

__weak修飾的對象,就算是使用了__block修飾,同樣仍是會被釋放掉,實際上書上的源代碼也是給了咱們這樣一個結論,只不過對象會自動置爲nil。而使用__unsafe_unretained修飾時,注意野指針問題。

8.Block循環引用

避免循環引用,根據Block的用途能夠選擇使用__block變量,__weak修飾符和__unsafe_unretained修飾符來避免循環引用。

__weak和__unsafe_unretained修飾弱引用,不用考慮釋放問題。

__block修飾,屬於強引用,須要在Block內對修飾的對象置nil,爲避免循環引用必須執行Block,可是__block變量能夠控制對象的持有時間。

相關文章
相關標籤/搜索