iOS - Block 原理探究(1)

2019-01-01

Aspects 源码的时候,发现了一段代码,当时不理解这个代码是什么意思,因此想要探究 block 的本质。以下是一点查阅资料并且自己测试后的一点见解。

typedef struct _AspectBlock {
	__unused Class isa;
	AspectBlockFlags flags;
	__unused int reserved;
	void (__unused *invoke)(struct _AspectBlock *block, ...);
	struct {
		unsigned long int reserved;
		unsigned long int size;
		// requires AspectBlockFlagsHasCopyDisposeHelpers
		void (*copy)(void *dst, const void *src);
		void (*dispose)(const void *);
		// requires AspectBlockFlagsHasSignature
		const char *signature;
		const char *layout;
	} *descriptor;
	// imported variables
} *AspectBlockRef;

// ......

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    // .......
}

1. 要想看 block 的底层实现,首先将 OC 代码转换成 C++ 代码

新建 block.m 文件添加以下 OC 代码:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        __block int b = 20;
        NSString *str = @"str";
        __weak NSString *weakStr = @"weakStr";
        void(^block)(int) = ^(int i){
            i;
            a;
            b;
            str;
            weakStr;
        };

        block(1);
    }
    return 0;
}

上述代码定义了各种类型的变量用于测试

终端 cd 到 block.m 根目录执行一下命令进行转换

clang  -rewrite-objc  block.m

这个时候会报 error

cannot create __weak reference because the current deployment target does not support weak references

stack overflow 上找到这个问题解决方案,通过以下命令进行转换

clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations block.m

这时候可以发现在当前目录会生成对应的 C++ 文件 block.cpp ,通过这个文件可以看到 block 在 C++ 中的实现,更好的理解 block 机制

找到其中对应 main 函数的代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
        NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_0__w7c3mlv94_10qs2mxt1kk0980000gn_T_block_3ee913_mi_0;
        __attribute__((objc_ownership(none))) NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_0__w7c3mlv94_10qs2mxt1kk0980000gn_T_block_3ee913_mi_1;
        void(*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, str, weakStr, (__Block_byref_b_0 *)&b, 570425344));

        ((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1);
    }
    return 0;
}

2. 解析 C++ 文件

2.1 __Block_byref_b_0

OC 代码

__block int b = 20;

对应 C++ 代码

__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};

实际上 b 就是以下类型

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

可以看出被 __block 修饰的 b 是一个与 OC 内存结构兼容的 struct

  • __isa: 默认为 (void*)0

  • __forwarding: 在构造函数中传入的是 (__Block_byref_b_0 *)&b ,是一个指向 b 的指针,在下面 block 的 C++ 实现(2.2 __main_block_impl_0)中可以看到构造函数传入的就是该指针,因此用 __block 修饰的变量实际传入 block 的是一个指向该变量的指针,因此可以对其进行修改
  • __size: 大小
  • b :b 的值 20
2.2 __main_block_impl_0

OC 代码

void(^block)(int) = ^(int i){
    i;
    a;
    b;
    str;
    weakStr;
};

对应 C++ 代码

void(*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, str, weakStr, (__Block_byref_b_0 *)&b, 570425344));

实际上 block 就是以下类型

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  NSString *__strong str;
  NSString *__weak weakStr;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *__strong _str, NSString *__weak _weakStr, __Block_byref_b_0 *_b, int flags=0) : a(_a), str(_str), weakStr(_weakStr), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到 block 实际也是一个结构体,内部包含一个 __block_impl 的结构体、__main_block_desc_0 结构体指针,以及用到的外部变量 abstrweakStr 和一个构造函数

通过观察构造函数,可以看出外部变量传递到 block 中时,只有用 __block 修饰过的 b 传入的是 __Block_byref_b_0 * 指针,对 b 的修改操作都是通过 _b->__forwarding 来直接对原始b 数据进行修改。而其他未用 __block 只是进行赋值操作,对 astrweakStr 的修改无法影响到 block 结构体内部的对应的属性的值。

因此会产生以下代码的差异

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        __block int b = 10;
        void(^block)(void) = ^ {
            NSLog(@"%d,%d", a, b);
        };
		a = 20;
		b = 20;
        block(); // 输出 10,20
    }
    return 0;
}
2.3 __block_impl

__block_impl 结构体的定义如下,保存 block 的实现

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • isa: 表示一个类对象指针,指向 block 的类。我们可以看到在 __main_block_impl_0 构造函数中有以下代码

    impl.isa = &_NSConcreteStackBlock;
    

    isa 被赋值为 &_NSConcreteStackBlock ,指向类 __NSStackBlock__ 的指针,说明了该 block 的是一个存放在栈区的 block,下面 3. Block 类型 有对 block 的类型进行介绍

  • Flags:标记位,标记 desc(2.4 __main_block_desc_0) 中有无 copy , dispose方法,有无方法签名字符 signature,layout 等
  • FuncPtr :指针指向 block 的具体实现,看 main 方法中传入的参数为方法 __main_block_func_0
2.4 __main_block_desc_0

__main_block_desc_0 结构体的定义如下,保存 block 描述信息

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*);
  // 以下内容在本例中没有,但是若 __block_impl 中 Flags 有标记位 1 << 30 则会有
  // const char *signature;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  • reserved: 0
  • Block_size:block 大小
  • copy:copy 传入的是方法 __main_block_copy_0,实现的功能就是当 block 进行 copy 时,会调用该方法。具体实现如下
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
  • dispose:dispose 传入的是方法 __main_block_dispose_0,实现的功能就是当 block 进行从堆中移除时调用。具体实现如下
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
  • signature:在本例中没有,但是若 block_impl(2.3 __block_impl) 中 Flags 有标记位 1 « 30 则会有,对方法参数的签名,对应 NSMethodSignature
2.5 __main_block_func_0

OC 代码

{
    i;
    a;
    b;
    str;
    weakStr;
};

对应 C++ 代码,方法 __main_block_func_0 定义如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  int a = __cself->a; // bound by copy
  NSString *__strong str = __cself->str; // bound by copy
  NSString *__weak weakStr = __cself->weakStr; // bound by copy

            i;
            a;
            (b->__forwarding->b);
            str;
            weakStr;
        }

__cself 指向 block 对象,只有用 __block 修饰过的变量会用特殊的方式调用 b->__forwarding->b

3. Block 类型

这里对 Block 类型的理解有点问题,后来重新测试 Block 的时候发现了我理解是有偏颇的,因此删除了这部分内容 一些我对 block 类型的理解重新写在了 iOS - Block 原理探究(2)