iOS Block clang 转 c++ 后底层解析

有一个类 BlockClass, 该类有一个类方法 +funcTest

情景一: 为什么外界修改了变量 a 的值, 调用block时打印的值不变?

#import "BlockClass.h"
@implementation BlockClass
+ (void) funcTest {
    int a = 10;
    NSLog(@"a1: %p, %d", &a,a);
    void(^blockName)(void) = ^{
        NSLog(@"block 内 a: %p, %d", &a,a);
    };
    a = 11;
    NSLog(@"a2: %p, %d", &a,a);
    blockName();
}

@end

执行 clang -rewrite-objc BlockClass.mOC 代码转化成 c++

编译后的 funcTest 函数

static void _C_BlockClass_funcTest(Class self, SEL _cmd) {
	// 声明临时变量 a
	int a = 10;
	
	// 打印变量 a 的地址, 值
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f764b9_mi_0, &a,a);
    
    // block 声明, 这里可以确定, block 的本质是一个结构体 __BlockClass__funcTest_block_impl_0
    void(*blockName)(void) = ((void (*)())&__BlockClass__funcTest_block_impl_0((void *)__BlockClass__funcTest_block_func_0, &__BlockClass__funcTest_block_desc_0_DATA, a));
    
    // 修改变量 a 的值
    a = 11;
    
    // 打印修改后的变量 a 的地址, 值
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f764b9_mi_2, &a,a);
    
    // 调用 block
    ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
}

这就是 block 的真面目, 结构体!

  1. impl: 保存了 block 的基本信息
  2. Desc: block 的描述
  3. a: block 捕获的外部变量
struct __BlockClass__funcTest_block_impl_0 {
  	struct __block_impl impl; 
  	struct __BlockClass__funcTest_block_desc_0* Desc;
    int a;
  	
  	/** 构造方法
  		注意这里, 把参数 _a 赋值给了成员变量 a, 这就是为什么外界修改了变量 a 的值, 调用block时打印的值不变, 因为 block 自己存了一份 a
  	*/
  	__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    	impl.isa = &_NSConcreteStackBlock;
    	impl.Flags = flags;
    	impl.FuncPtr = fp;
    	Desc = desc;
  	}
};

// block 的描述, 这里比较简单, 后面有复杂的案例
static struct __BlockClass__funcTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __BlockClass__funcTest_block_desc_0_DATA = { 0, sizeof(struct __BlockClass__funcTest_block_impl_0)};

// 保存了 block 的基本信息
struct __block_impl {
  	void *isa; // block 类型
    void *FuncPtr; // 函数体指针
  	int Flags;
  	int Reserved;
};


// 就是 block 的 {}函数体
static void __BlockClass__funcTest_block_func_0(struct __BlockClass__funcTest_block_impl_0 *__cself) {
	// 获取 a 的值:  __cself 的成员变量 a, 不是外部 a 变量!
  	int a = __cself->a; 
        	NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f619af_mi_0, &a,a);
}

打印结果也说明了 block 对外部变量的操作: 基本数据类型(值类型)赋值, 可以理解为深拷贝

Test1[43462:5694354] a1: 0x7ffee904c0dc, 10
Test1[43462:5694354] a2: 0x7ffee904c0dc, 11
Test1[43462:5694354] block 内 a: 0x600003de56d0, 10

情景二: block 访问 局部静态变量, 捕获的是它的指针

修改变量 a 为静态变量

+ (void) funcTest {
    static int a = 10;
    NSLog(@"a1: %p, %d", &a,a);
    void(^blockName)(void) = ^{
    	a++;
        NSLog(@"block 内 a: %p, %d", &a,a);
    };
    a = 11;
    NSLog(@"a2: %p, %d", &a,a);
    blockName();
}

block 结构体捕获了静态变量 a 的地址

struct __BlockClass__funcTest_block_impl_0 {
  	struct __block_impl impl;
  	struct __BlockClass__funcTest_block_desc_0* Desc;
  	int *a;
  	__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    	impl.isa = &_NSConcreteStackBlock;
    	impl.Flags = flags;
    	impl.FuncPtr = fp;
    	Desc = desc;
  	}
};
Test1[43292:5690290] a1: 0x10b8b9748, 10
Test1[43292:5690290] a2: 0x10b8b9748, 11
Test1[43292:5690290] block 内 a: 0x10b8b9748, 12

情景三: block 访问全局变量(包括全局静态变量), 不会捕获

代码稍作修改, a 变成全局变量, block 内直接修改 a

int a = 10; // static int a = 10;
+ (void) funcTest {
    NSLog(@"a1: %p, %d", &a,a);
    void(^blockName)(void) = ^{
        a ++;
        NSLog(@"block 内 a: %p, %d", &a,a);
    };
    a = 11;
    NSLog(@"a2: %p, %d", &a,a);
    blockName();
}

执行 clang 编译成 c++, 我们发现, block 结构体里没有变量 a, 说明 block 没有捕获全局变量 a, 使用的时候直接访问全局变量

struct __BlockClass__funcTest_block_impl_0 {
  struct __block_impl impl;
  struct __BlockClass__funcTest_block_desc_0* Desc;
  __BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
Test1[43415:5693256] a1: 0x105013748, 10
Test1[43415:5693256] a2: 0x105013748, 11
Test1[43415:5693256] block 内 a: 0x105013748, 12

情景四: 为什么给外部变量添加 __block 修饰符后, 就可以在 block 内修改外部变量的值

总结: 被 __block 修饰后的变量 会被包装成 __Block_byref_a_0 结构体, 通过结构体指针访问真正的变量.

我们把代码修改为如下, 仅给变量 a 添加 __block 修饰符

+ (void) funcTest {
    __block int a = 10;
    NSLog(@"a1: %p, %d", &a,a);
    void(^blockName)(void) = ^{
        a++;
        NSLog(@"block 内 a: %p, %d", &a,a);
    };
    a = 11;
    NSLog(@"a2: %p, %d", &a,a);
    blockName();
}

同样, 执行 clang -rewrite-objc BlockClass.mOC 代码转化成 c++. 下面是与情景一 不同的地方

编译后的 funcTest 函数

  1. __block 修饰变量 a 后, a 被包装成了结构体, 以指针传递给 block 结构体, 引用类型赋值仅仅指针 copy, 指向同一块内存, 浅拷贝.
  2. 使用 a 的时候其实是去找结构体 (__Block_byref_a_0 )a.__forwarding->a, 真正的 a.
  3. 构造 block 结构体时, 传递的是指针 __Block_byref_a_0*, 所以这时 block 内部就可以修改外部变量了.
static void _C_BlockClass_funcTest(Class self, SEL _cmd) {
	// 1. 关键!!! 创建了一个 __Block_byref_a_0对象 (不是指针哦), 第二个参数 __forwarding 保存的是它自己或被拷贝到堆上的地址(这里有点绕).
	__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
	// 2. 注意!!! 后续对 a 的操作, 都是操作的结构体a.__forwarding->a
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_0, &(a.__forwarding->a),(a.__forwarding->a));
    // 3. block 结构体第 3 个参数 a 是 __Block_byref_a_0 指针类型哦
    void(*blockName)(void) = ((void (*)())&__BlockClass__funcTest_block_impl_0((void *)__BlockClass__funcTest_block_func_0, &__BlockClass__funcTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    // 修改真正的 a 的值
    (a.__forwarding->a) = 11;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_2, &(a.__forwarding->a),(a.__forwarding->a));
    
    ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
}

这就是 int a 转化后的结构体

struct __Block_byref_a_0 {
	void *__isa;
	// 保存了自己或被拷贝到堆上的内存地址
	__Block_byref_a_0 *__forwarding;
 	int __flags;
 	int __size;
 	// 真正的 a
 	int a;
};

block 结构体有引用的外部变量 a 类型由int =>__Block_byref_a_0*, 指针!!

struct __BlockClass__funcTest_block_impl_0 {
  	struct __block_impl impl;
  	struct __BlockClass__funcTest_block_desc_0* Desc;
  
	__Block_byref_a_0 *a; 
  	__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    	impl.isa = &_NSConcreteStackBlock;
    	impl.Flags = flags;
    	impl.FuncPtr = fp;
    	Desc = desc;
  	}
};

函数体

static void __BlockClass__funcTest_block_func_0(struct __BlockClass__funcTest_block_impl_0 *__cself) {
	// 获取包装后的 a
	__Block_byref_a_0 *a = __cself->a;
	int b = 1;
	// 操作 a: 拿到真正的a
	NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_1, &(a->__forwarding->a),(a->__forwarding->a));
    }

Desc 结构体也有改变, 多了两个函数 copydispose.
在捕获变量为__block修饰的基本类型 (会被转化为结构体对象),或者为对象时,block才会有这两个辅助函数。

static struct __BlockClass__funcTest_block_desc_0 {
  	size_t reserved;
  	size_t Block_size;
  	void (*copy)(struct __BlockClass__funcTest_block_impl_0*, struct __BlockClass__funcTest_block_impl_0*);
  	void (*dispose)(struct __BlockClass__funcTest_block_impl_0*);
} __BlockClass__funcTest_block_desc_0_DATA = { 0, sizeof(struct __BlockClass__funcTest_block_impl_0), __BlockClass__funcTest_block_copy_0, __BlockClass__funcTest_block_dispose_0};

static void __BlockClass__funcTest_block_copy_0(struct __BlockClass__funcTest_block_impl_0*dst, struct __BlockClass__funcTest_block_impl_0*src) {
	_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __BlockClass__funcTest_block_dispose_0(struct __BlockClass__funcTest_block_impl_0*src) {
	_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

_NSConcreteMallocBlock 是由_NSConcreteStackBlock类型的 block 拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。
block 的拷贝最终都会调用_Block_copy_internal函数,函数内判断是否调用 descriptor->copy , 看上面代码 __BlockClass__funcTest_block_copy_0 内部调用了 _Block_object_assign 方法, 该方法根据变量的修饰符 (强引用: __strong ; 弱引用: __weak__unsafe_unretained), 强引用或弱引用该变量.

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
	...
    aBlock = (struct Block_layout *)arg;
	...
    // Its a stack block.  Make a copy.
    if (!isGC) {
    	// 申请block的堆内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷贝栈中block到刚申请的堆内存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改变isa指向_NSConcreteMallocBlock,即堆block类型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            // 调用 Desc 的 copy 函数
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

遗留问题: 打印结果让我很疑惑, 初始化的 a 的地址竟然与后面两个不一致? 看代码都是用的 结构体 a 的指针啊, 很疑惑.

Test1[43581:5697263] a1: 0x7ffee713b0d8, 10
Test1[43581:5697263] a2: 0x600000171ff8, 11
Test1[43581:5697263] block 内 a: 0x600000171ff8, 12

循环引用问题

从上面的分析, 我们知道 block 也是一个对象,
block 被拷贝到堆上时,会调用 block 内部的 copy 方法, copy 方法内部会调用 _Block_object_assign 函数, 它会根据变量的修饰符(__strong__weak__unsafe_unretained)做出相应的操作.

__strong 修饰的对象被 block 捕获时, block 会强引用它.
可以使用 __weak__unsafe_unretained 修饰 block 捕获的对象, 使 block 不强引用对象, 解决循环引用.

  • __weak
    1.对象释放后会 weak 指针自动致为 nil;
    2.对象会被注册到 autoreleasepool 中;
    3.只能修饰对象,不能修饰基本数据类型, 只能在 ARC 模式下使用。

  • __unsafe_unretained
    __weak 相比, 当对象被销毁时, __unsafe_unretained 修饰的指针不会被指向 nil, 有野指针的风险


Block技巧与底层解析

你可能感兴趣的:(知识点记录,ios,c++)