理解 Block 實現原理

Block 是一種帶有自動變量值的匿名函數。git

Block 在 iOS 平常開發中會頻繁使用到,使用起來也十分方便,而它的實現原理和機制不少小夥伴卻一無所知。 Block 是一種帶有自動變量值的匿名函數,它可以自動捕獲函數內使用到的參數,本文將從細節分析 Block 的實現原理。github

1、Block 的實現

在探尋 Block 實現原理中,命令行工具Clang是很是實用的,它能夠將其轉換成 C++ 源碼,方便咱們瞭解其中的實現原理。編程

clang -rewrite-objc main.m
複製代碼

咱們能夠利用上面的命令,嘗試將下面這段代碼轉換成 C++ 源碼,進而分析 Block 的具體實現:數組

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int tempVar = 1;
        void (^blk)(void) = ^() {
            printf("Block var:%d\n", tempVar);
        };
        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;
  int tempVar;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int tempVar = __cself->tempVar; // bound by copy
  printf("Block var:%d\n", tempVar);
}

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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int tempVar = 1;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
複製代碼

轉換後獲得了一大串代碼,接下來咱們一一分析這段代碼的實際意義。bash

第一部分是__block_impl,它是Block實現的最底層的結構體:框架

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
複製代碼
  • isa:代表具備它和對象同樣特性。
  • Flag:爲狀態標誌位。
  • Reserved:升級預留內存大小。
  • FuncPtr:函數指針。

第二部分__main_block_desc_0是一個管理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)};
複製代碼
  • Reserved:升級預留內存大小。
  • Block_size:Block 的大小。

第三部分爲Block實現結構體:工具

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

該結構體的命名邏輯爲__函數名_block_impl_函數內順序,接下來查看結構體成員:ui

  • impl:爲__block_impl類型結構體,參考第一部分。
  • Desc:爲__main_block_impl_0結構體實例大小。
  • tempVar:捕獲的自動變量值。
  • __main_block_impl_0:爲__main_block_impl_0結構體的構造函數。

第四部分爲Block的函數指針指向的函數__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int tempVar = __cself->tempVar; // bound by copy
  printf("Block var:%d\n", tempVar);
}
複製代碼

__main_block_impl_0結構體中將捕獲的自動變量值做爲成員變量,調用時先獲取結構體成員變量的值,而後複製使用。

第五部分爲main函數轉換後源碼:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int tempVar = 1;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
複製代碼

除了一個局部變量tempVar以外,另外 2 行代碼分別是Block的的初始化部分和調用部分。去除部分類型強轉代碼後以下:

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

第一行代碼是將前面聲明的__main_block_func_0&__main_block_desc_0_DATA傳入構造函數,獲得blk結構體實例。

第二行代碼則是調用blk的函數指針。

2、捕獲變量值

2.1 自動變量

在 Block 中使用外部的局部變量時,會自動捕獲該變量而且成爲 Block 結構體的成員變量,以便在 Block 內部訪問該變量。除此以外,有其餘幾種方式能夠訪問外部變量,下面是變量類型和對應的做用域:

  1. 自動變量:捕獲至 Block 內。
  2. 靜態變量:做用域內可用。
  3. 全局變量:整個程序可用。
  4. 靜態全局變量:當前文件可用。

經過將下面的代碼轉換至 C++ 代碼,分析 Block 中各類類型變量的訪問方式:

static char globalVar[] = {"globalVar"};
static char globalStaticVar[] = {"globalStaticVar"};

void catchVar() {
    int var1 = 1;
    int var2 = 2;
    static char staticVar[] = {"staticVar"};
    
    void (^blk)(void) = ^{
        printf("%d\n", var1);
        printf("%s\n", staticVar);
        printf("%s\n", globalVar);
        printf("%s\n", globalStaticVar);
    };
    blk();
}
複製代碼

上面的代碼分別使用了局部變量、靜態變量、全局變量和靜態全局變量,其轉換後的代碼:

struct __catchVar_block_impl_0 {
  struct __block_impl impl;
  struct __catchVar_block_desc_0* Desc;
  int var1; // 局部變量
  char (*staticVar)[10]; // 靜態變量
  __catchVar_block_impl_0(void *fp, struct __catchVar_block_desc_0 *desc, int _var1, char (*_staticVar)[10], int flags=0) : var1(_var1), staticVar(_staticVar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchVar_block_func_0(struct __catchVar_block_impl_0 *__cself) {
  int var1 = __cself->var1; // bound by copy
  char (*staticVar)[10] = __cself->staticVar; // bound by copy

        printf("%d\n", var1);
        printf("%s\n", (*staticVar));
        printf("%s\n", globalVar);
        printf("%s\n", globalStaticVar);
    }

static struct __catchVar_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __catchVar_block_desc_0_DATA = { 0, sizeof(struct __catchVar_block_impl_0)};
void catchVar() {
    int var1 = 1;
    int var2 = 2;
    static char staticVar[] = {"staticVar"};

    void (*blk)(void) = ((void (*)())&__catchVar_block_impl_0((void *)__catchVar_block_func_0, &__catchVar_block_desc_0_DATA, var1, &staticVar));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

經過__catchVar_block_func_0函數和 Block 構造函數能夠知道各類變量的訪問方式:

  • 全局變量靜態全局變量 因其做用域內均可以直接訪問。
  • 靜態變量 成爲成員變量,可是從構造函數傳入的是一個內存地址,而後經過地址訪問。
  • 局部變量 成爲成員變量,從構造函數直接傳入變量的值並賦值給成員變量,而後經過成員變量訪問。

2.2 對象

下面的代碼中,Block 內使用了外部的一個對象,這種狀況下 Block 內部是如何捕獲該對象的呢?

void catchObject() {
    id obj = [NSObject new];
    
    void (^blk)(void) = ^{
        printf("%d\n", [obj hash]);
    };
    blk();
}
複製代碼

咱們將上面的代碼轉換成 C++ 代碼後分析其中實現原理:

struct __catchObject_block_impl_0 {
  struct __block_impl impl;
  struct __catchObject_block_desc_0* Desc;
  __strong id obj;
  __catchObject_block_impl_0(void *fp, struct __catchObject_block_desc_0 *desc, __strong id _obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchObject_block_func_0(struct __catchObject_block_impl_0 *__cself) {
  __strong id obj = __cself->obj; // bound by copy

        printf("%d\n", ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("hash")));
    }
static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __catchObject_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __catchObject_block_impl_0*, struct __catchObject_block_impl_0*);
  void (*dispose)(struct __catchObject_block_impl_0*);
} __catchObject_block_desc_0_DATA = { 0, sizeof(struct __catchObject_block_impl_0), __catchObject_block_copy_0, __catchObject_block_dispose_0};


void catchObject() {
    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));

    void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

首先來看catchObject()函數,在構建 blk 時,傳入了對象obj和十進制標誌位570425344

void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
複製代碼

Block 結構體中成員變量obj__strong修飾符,傳入的對象obj直接賦值給成員變量,說明是直接使用原對象而且使引用計數 +1 。

其次是源代碼中新增了兩個方法,分別是__catchObject_block_copy_0__catchObject_block_dispose_0,而這兩個方法又分別調用了_Block_object_assign_Block_object_dispose方法,這兩個方法是用來管理 Block 中變量存儲的,後面會進行分析。

static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {
  _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {
  _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼

2.3 Block

將一個 Block 做爲另一個 Block 內的參數來使用,接下來分析這種狀況下 Block 的實現。

void catchBlock() {
    void (^block)(void) = ^{};
    void (^blk)(void) = ^{
        block;
    };
    blk();
}
複製代碼

轉換後代碼以下:

struct __catchBlock_block_impl_0 {
  struct __block_impl impl;
  struct __catchBlock_block_desc_0* Desc;
  __catchBlock_block_impl_0(void *fp, struct __catchBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlock_block_func_0(struct __catchBlock_block_impl_0 *__cself) {
}

static struct __catchBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __catchBlock_block_desc_0_DATA = { 0, sizeof(struct __catchBlock_block_impl_0)};

struct __catchBlock_block_impl_1 {
  struct __block_impl impl;
  struct __catchBlock_block_desc_1* Desc;
  struct __block_impl *block;
  __catchBlock_block_impl_1(void *fp, struct __catchBlock_block_desc_1 *desc, void *_block, int flags=0) : block((struct __block_impl *)_block) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlock_block_func_1(struct __catchBlock_block_impl_1 *__cself) {
  void (*block)() = (void (*)())__cself->block; // bound by copy

        block;
    }
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static struct __catchBlock_block_desc_1 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __catchBlock_block_impl_1*, struct __catchBlock_block_impl_1*);
  void (*dispose)(struct __catchBlock_block_impl_1*);
} __catchBlock_block_desc_1_DATA = { 0, sizeof(struct __catchBlock_block_impl_1), __catchBlock_block_copy_1, __catchBlock_block_dispose_1};

void catchBlock() {
    void (*block)(void) = ((void (*)())&__catchBlock_block_impl_0((void *)__catchBlock_block_func_0, &__catchBlock_block_desc_0_DATA));
    void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

一樣的先看catchBlock()函數,其中 Block 構造函數中傳入了block和標誌位570425344,賦值給在__catchBlock_block_impl_1結構體中的一個__block_impl類型的結構體成員變量block

void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
複製代碼

在這段代碼中一樣有__catchObject_block_copy_0__catchObject_block_dispose_0兩個方法,不一樣的是調用_Block_object_assign_Block_object_dispose方法時最後一個入參爲7 /*BLOCK_FIELD_IS_BLOCK*/,以前的捕獲對象時傳入的參數是3 /*BLOCK_FIELD_IS_OBJECT*/

static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {
  _Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}

static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {
  _Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
複製代碼

2.4 __block 修飾的變量

Block 將外部的變量捕獲後,能夠在內部訪問外部的變量,可是還不能修改外部變量的值(靜態變量、全局變量和靜態全局變量能夠直接修改)。這個時候須要使用 __block 修飾符,使得在 Block 內部也能夠修改 __block 修飾符修飾的變量。

下面經過轉換源碼來分析實現原理:

void catchBlockVar() {
    __block int blockVar = 1;
    
    void (^blk)(void) = ^{
        blockVar = 2;
        printf("%d\n", blockVar);
    };
    blk();
}
複製代碼

轉換後:

struct __Block_byref_blockVar_0 {
  void *__isa;
__Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 int blockVar;
};

struct __catchBlockVar_block_impl_0 {
  struct __block_impl impl;
  struct __catchBlockVar_block_desc_0* Desc;
  __Block_byref_blockVar_0 *blockVar; // by ref
  __catchBlockVar_block_impl_0(void *fp, struct __catchBlockVar_block_desc_0 *desc, __Block_byref_blockVar_0 *_blockVar, int flags=0) : blockVar(_blockVar->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
  __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref

        (blockVar->__forwarding->blockVar) = 2;
        printf("%d\n", (blockVar->__forwarding->blockVar));
}
    
static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __catchBlockVar_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __catchBlockVar_block_impl_0*, struct __catchBlockVar_block_impl_0*);
  void (*dispose)(struct __catchBlockVar_block_impl_0*);
} __catchBlockVar_block_desc_0_DATA = { 0, sizeof(struct __catchBlockVar_block_impl_0), __catchBlockVar_block_copy_0, __catchBlockVar_block_dispose_0};


void catchBlockVar() {
    __attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};

    void (*blk)(void) = ((void (*)())&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

和其餘類型 Block 最大的不一樣就是多了一個結構體__Block_byref_blockVar_0,通過__block修飾符修飾的變量都會自動生成一個這樣的結構體。

struct __Block_byref_blockVar_0 {
  void *__isa;
__Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 int blockVar;
};
複製代碼
  • __isa:具體和對象同樣的特性。
  • __forwarding:在棧區且未被複制時指向本身,被複制到堆區後指向堆區的結構體。
  • __flags:標誌位。
  • __size:結構體佔用內存大小。
  • blockVar:原變量值。

catchBlockVar方法轉換後,以前的int類型變量blockVar變成__Block_byref_blockVar_0類型結構體,而後將此結構體地址傳入 Block 的構造函數中,所以 Block 自動生成的成員變量也爲__Block_byref_blockVar_0類型。

void catchBlockVar() {
    __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};

    void (*blk)(void) = (&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼

在被 Block 被調用時,經過下面的代碼能夠發現,訪問blockVar並不直接訪問,而是經過其__forwarding來訪問其原變量的值。這樣能夠在它被複制到堆區時,訪問堆區中的結構體。爲何要優先訪問堆區的結構體?棧區的對象在超出其做用域後會被釋放,若是但願在做用域外使用就須要複製到堆區中。

static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
  __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref

        (blockVar->__forwarding->blockVar) = 2;
        printf("%d\n", (blockVar->__forwarding->blockVar));
}
複製代碼

在這段代碼中的__catchObject_block_copy_0__catchObject_block_dispose_0兩個方法中傳入的參數是8 /*BLOCK_FIELD_IS_BYREF*/

static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {
  _Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {
  _Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼

2.5 __block 修飾的對象

在 Block 使用__block修飾的變量和__block修飾的對象,其中內部實現是有一些細微區別的,經過下面的代碼來進行分析。

void catchBlockObject() {
    
    __block NSObject *obj = [[NSObject alloc] init];
    blk_t block = ^ {
        obj;
    };
}
複製代碼

轉換後:

struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __catchBlockObject_block_impl_0 {
  struct __block_impl impl;
  struct __catchBlockObject_block_desc_0* Desc;
  __Block_byref_obj_1 *obj; // by ref
  __catchBlockObject_block_impl_0(void *fp, struct __catchBlockObject_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlockObject_block_func_0(struct __catchBlockObject_block_impl_0 *__cself) {
  __Block_byref_obj_1 *obj = __cself->obj; // bound by ref

        (obj->__forwarding->obj);
    }
static void __catchBlockObject_block_copy_0(struct __catchBlockObject_block_impl_0*dst, struct __catchBlockObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __catchBlockObject_block_dispose_0(struct __catchBlockObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __catchBlockObject_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __catchBlockObject_block_impl_0*, struct __catchBlockObject_block_impl_0*);
  void (*dispose)(struct __catchBlockObject_block_impl_0*);
} __catchBlockObject_block_desc_0_DATA = { 0, sizeof(struct __catchBlockObject_block_impl_0), __catchBlockObject_block_copy_0, __catchBlockObject_block_dispose_0};


void catchBlockObject() {

    __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __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"))};

    blk_t block = ((void (*)())&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製代碼

在聲明的結構體__Block_byref_obj_1中,和以前不同的是多了__Block_byref_id_object_copy__Block_byref_id_object_dispose兩個管理內存的方法。

struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};
複製代碼

再看到catchBlockObject()函數中,被__block修飾符修飾的obj對象轉換成__Block_byref_obj_1類型結構體。其中copydispose兩個方法傳入的__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131兩個靜態方法。

void catchBlockObject() {

   __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __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"))};

    blk_t block = (&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製代碼

靜態方法以下,在最後一個參數傳入的是131,其實就是3 + 128

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

經過下面這個枚舉能夠明白,爲何捕獲不一樣類型的變量,須要不一樣的入參。根據入參不一樣,對捕獲的變量複製和釋放的操做都是不一樣的。131則表示BLOCK_FIELD_IS_BYREF|BLOCK_BYREF_CALLER

// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
複製代碼

Block 自己的 Copy / Dispose 方法入參仍是8 /*BLOCK_FIELD_IS_BYREF*/

3、Block 的存儲域

Block 的存儲域分爲 3 種,分別爲_NSConcreteStackBlock_NSConcreteGlobalBlock_NSConcreteMallocBlock

  • _NSConcreteStackBlock:棧區
  • _NSConcreteGlobalBlock:數據區域(.data 區)
  • _NSConcreteMallocBlock:堆區

3.1 _NSConcreteStackBlock

正常狀況下,定義在類內部的 Block 在捕獲了自動變量的狀況下都是在棧區,能夠經過下面的代碼打印出其類型。可是在實際使用中都會定義後都會賦值給一個變量,這會致使實際使用用這個 Block 的時候已經變成_NSConcreteMallocBlock類型。

// block 使用了捕獲的變量 tempVar
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int tempVar = 1;
        NSLog(@"Stack Block:%@\n", ^() {
            printf("Stack Block! %d\n", tempVar);
        });
    }
    return 0;
}

// printf:Stack Block:<__NSStackBlock__: 0x7ffeefbff4a0>
複製代碼

3.2 _NSConcreteGlobalBlock

在定義全局變量的區域定義的 Block 類型爲_NSConcreteGlobalBlock,另外還有一種狀況就是定義在類內部的 Block 在沒有捕獲任何自動變量時,也是_NSConcreteGlobalBlock類型。

// block 內未使用外部變量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Global Block:%@\n", ^() {
            printf("Global Block!\n");
        });
    }
    return 0;
}

// printf:Global Block:<__NSGlobalBlock__: 0x1000021c8>
複製代碼

3.3 _NSConcreteMallocBlock

由於 Block 捕獲的 __block 變量存在棧區時,超出其做用域後則被釋放。爲了不這種狀況,Block 實現機制將 Block 從棧區複製到堆區,這樣即便超出其做用域,堆區的 Block 和 __block 變量依然存在。

在未被複制時,__block 變量__forwarding指向自身,被複制後指向堆區中的 __block 變量,這種機制使其不管是在堆區仍是棧區均可以正確訪問。

下面這些場景下編譯器會自動處理將 Block 拷貝到堆上:

  • ARC 有效時 block 做爲函數或方法的返回值會自動被拷貝到堆上
  • Cocoa 框架中的方法名包含 usingBlock 等時
  • GCD 的 API
  • 將 block 賦值給 __Strong 修飾符 id 類型對象或 Block 類型的成員變量時

如下場景須要手動拷貝至堆上

  • 手動調用 block 實例方法
    • 將 block 做爲方法中的參數時須要開發者手動拷貝
    • 當將 block 放入數組並做爲返回值時須要手動拷貝

4、 __block 變量的存儲域

當 Block 從棧區被複制到堆區時,對應的__block修飾符修飾的變量也相應地被複制到堆區。

在前面的內容咱們分析到__block修飾的變量會轉換成一個結構體,結構體中含有成員變量__forwarding,複製到堆區後能夠在 Block 變量超出其做用域使用,這個時候棧區結構體成員變量__forwarding指向堆區的結構體(在未被複制時指向自身結構體)。

當多個 Block 使用同一個 __block 變量時,複製已經在堆上的 __block 變量引用計數會增長,當釋放時也是將減引用計數減至 0 後才廢棄該 __block 變量。

5、_Block_object_assign 和 _Block_object_dispose

在第二章節中介紹了捕獲不一樣類型的變量時,Block 調用_Block_object_assign函數的入參flags都不同,下面來看看具體實現。

5.1 _Block_object_assign

直接看實現源碼:

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
複製代碼

由上面代碼可知_Block_object_assign方法根據入參flags作了不一樣的處理,下面一一分析不一樣入參的處理方式:

5.1.1 BLOCK_FIELD_IS_OBJECT:複製對象

// 默認_Block_retain_object 被賦值爲 _Block_retain_object_default 即什麼都不作
_Block_retain_object(object); 
// 指針指向原對象內存地址。
*dest = object; 
複製代碼

_Block_retain_object方法在 _Block_use_RR2被執行時纔有實際意義。

void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
    _Block_retain_object = callbacks->retain;
    _Block_release_object = callbacks->release;
    _Block_destructInstance = callbacks->destructInstance;
}
複製代碼

5.1.2 BLOCK_FIELD_IS_BLOCK:複製 Block

分析_Block_copy的實現:

//複製或碰撞引用計數。若是確實要複製,請調用複製助手(若是存在)。
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    aBlock = (struct Block_layout *)arg;
    // 已複製 則增長引用計數
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 若是是全局 Block 則直接返回
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 進行復制
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
複製代碼

5.1.3 BLOCK_FIELD_IS_BYREF:複製 _block 變量

分析_Block_byref_copy的實現:

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 複製 Block_byref
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
        
            // 複製 Block_byref2,含有 copy / dispose 方法的變量須要執行這部分代碼
            
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                // 複製 Block_byref3
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // 已經複製到堆上的 引用計數 +1
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
複製代碼

5.1.4 其餘

在其餘狀況下都是直接指針指向原對象地址:

*dest = object;
複製代碼

5.2 _Block_object_dispose

下面是 Block 捕獲變量的釋放邏輯:

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}
複製代碼

5.2.1 BLOCK_FIELD_IS_BYREF:釋放 _block 變量

分析_Block_byref_release的實現:

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) byref = byref->forwarding; // 判斷是否被複制到堆上 if (byref->flags & BLOCK_BYREF_NEEDS_FREE) { int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; os_assert(refcount); // 引用計數 -1 後判斷是否要進行釋放操做 if (latching_decr_int_should_deallocate(&byref->flags)) { // 判斷這個變量是否有 copy / dispose 方法 if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1); (*byref2->byref_destroy)(byref); } free(byref); } } } 複製代碼

須要根據 _block 變量成員變量和標誌位來判斷釋放步驟。

5.2.2 BLOCK_FIELD_IS_BLOCK:釋放 Block

分析_Block_release的實現:

void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    // 全局 Block 和 棧上的 Block 直接返回
    
    // Block 引用計數 -1 後判斷是否須要進行釋放
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}
複製代碼

5.2.3 BLOCK_FIELD_IS_OBJECT:釋放對象

_Block_release_object(object);
複製代碼

_Block_release_object方法在 ARC 環境下無操做,和_Block_retain_object同樣。

6、循環引用

一個 Block 做爲類的成員變量的同時,Block 內部還使用了類實例的狀況下會引起循環引用。在這種狀況下,類實例持有成員變量 block ,block 持有成員變量 __block 變量,__block 變量結構體持有類實例,造成一個三角循環引用關係。

__block id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
}
複製代碼

解決循環引用的方法有 2 種,一種是使用 __weak 修飾符,這種方法打破了__block 變量結構體持有類實例的關係,從而避免循環引用。

__weak id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
}
複製代碼

還有一種狀況是使用 __block 修飾符,而後 blk 調用函數最後一行將tmp手動置空,這種方法雖然也能夠避免循環引用,可是一旦 blk 沒有被調用的話,一樣會形成循環引用。因此仍是使用 __weak 修飾符的方式更爲安全。

__block id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
  tmp = nil;
}
複製代碼

總結

整片文章寫下來,最重要的幾個概念:

  • 捕獲不一樣類型變量的 Block 會生成不一樣的邏輯。
  • Block 的做用域的機制是爲了 Block 和內部使用的變量在超出其做用域仍能使用。
  • 針對不一樣類型的變量或 __block 變量結構體, 複製和釋放邏輯也不同。

參考


點此查看更多個人文章

相關文章
相關標籤/搜索