理解 Block 实现原理

栏目: IOS · 发布时间: 4年前

内容简介:Block 是一种带有自动变量值的匿名函数。Block 在 iOS 日常开发中会频繁使用到,使用起来也十分方便,而它的实现原理和机制很多小伙伴却一无所知。 Block 是一种带有自动变量值的匿名函数,它能够自动捕获函数内使用到的参数,本文将从细节分析 Block 的实现原理。在探寻 Block 实现原理中,命令行工具

Block 是一种带有自动变量值的匿名函数。

Block 在 iOS 日常开发中会频繁使用到,使用起来也十分方便,而它的实现原理和机制很多小伙伴却一无所知。 Block 是一种带有自动变量值的匿名函数,它能够自动捕获函数内使用到的参数,本文将从细节分析 Block 的实现原理。

一、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;
}
复制代码

转换后得到了一大串代码,接下来我们一一分析这段代码的实际意义。

第一部分是 __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_函数内顺序 ,接下来查看结构体成员:

__block_impl
__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.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*/

三、Block 的存储域

Block 的存储域分为 3 种,分别为 _NSConcreteStackBlock_NSConcreteGlobalBlock_NSConcreteMallocBlock

  • _NSConcreteStackBlock:栈区
  • _NSConcreteGlobalBlock:数据区域(.data 区)
  • _NSConcreteMallocBlock:堆区
理解 Block 实现原理

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 实现原理
理解 Block 实现原理

在未被复制时,__block 变量 __forwarding 指向自身,被复制后指向堆区中的 __block 变量,这种机制使其无论是在堆区还是栈区都可以正确访问。

下面这些场景下编译器会自动处理将 Block 拷贝到堆上:

  • ARC 有效时 block 作为函数或方法的返回值会自动被拷贝到堆上
  • Cocoa 框架中的方法名包含 usingBlock 等时
  • GCD 的 API
  • 将 block 赋值给 __Strong 修饰符 id 类型对象或 Block 类型的成员变量时

以下场景需要手动拷贝至堆上

  • 手动调用 block 实例方法
    • 将 block 作为方法中的参数时需要开发者手动拷贝
    • 当将 block 放入数组并作为返回值时需要手动拷贝

四、 __block 变量的存储域

当 Block 从栈区被复制到堆区时,对应的 __block 修饰符修饰的变量也相应地被复制到堆区。

理解 Block 实现原理

在前面的内容我们分析到 __block 修饰的变量会转换成一个结构体,结构体中含有成员变量 __forwarding ,复制到堆区后可以在 Block 变量超出其作用域使用,这个时候栈区结构体成员变量 __forwarding 指向堆区的结构体(在未被复制时指向自身结构体)。

理解 Block 实现原理

当多个 Block 使用同一个 __block 变量时,复制已经在堆上的 __block 变量引用计数会增加,当释放时也是将减引用计数减至 0 后才废弃该 __block 变量。

理解 Block 实现原理

五、_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 一样。

六、循环引用

一个 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 实现原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective JavaScript

Effective JavaScript

David Herman / Addison-Wesley Professional / 2012-12-6 / USD 39.99

"It's uncommon to have a programming language wonk who can speak in such comfortable and friendly language as David does. His walk through the syntax and semantics of JavaScript is both charming and h......一起来看看 《Effective JavaScript》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具