Block的实现

参考文章
ibireme的博客文章
iOS 高级编程

本文属于原创,转载请注明出处

简介
  • Block截获自动变量相当于截获了变量的值,之后在Block外改变变量的值不会影响Block中的值
  • Block中截获的自动变量不能修改,修改会报错,修改要给变量加__block修饰符,在Block调用后,原变量的值也会改变,但被__block修饰符修饰的变量已经不是单纯的变量了
  • 截获并更改对象不会报错,赋值会报错,了解引用类型就知道原因,这里不再赘述

Block的实质

int main() 
{
	void (^blk)(void) = ^ { printf("Block\n"); };
	blk();
	return 0;  
}

通过以下规则

  • 根据Block所属的函数名(此处为main),和该Block在该函数出现的顺序值,此处为0,来给Clang变换的函数命名
  • 此处的__cself相当于self,为指向当前Block的变量

以上代码被转为

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;
	__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\n");
}
int main()
{
	void (*blk)(void) = (void(*)void)&__main_block_impl_0(void*)__main_block_func_0, &__main_block_desc_0_DATA);
	((void (*) (struct __block_impl *))(struct __block_impl *) blk);
	return 0;
}

几个重要的结构体和函数

  • __block_impl:为Block的结构体,存放的isa指针指向当前对象,FuncPtr指向当前Block保存的实现函数
  • __main_block_desc_0:保存了Block的状态版本信息
  • __main_block_func_0:保存了当前Block所实现的函数
  • __main_block_impl_0:封装了Block的结构体和装有状态信息的__main_block_desc_0,提供一个构造函数

以上生成和调用Block的过程可以被概述为:

  1. 定义Block变量相当于调用__main_block_impl_0的构造函数,通过静态函数指针__main_block_func_0和结构体指针__main_block_desc_0初始化
  2. 在构造函数内部,将传递进来的__main_block_func_0函数使用isa为Block结构体中的成员变量FuncPtr初始化
  3. 调用Block时,使用isa指针取出Block中的FuncPtr函数进行调用
截获自动变量
int main()
{
	int val = 10;
	void (^blk)(void) = ^ { printf("val"); };
	blk() 
}

该源码中Block函数部分相当于

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0* Desc;
	int val;
}

// __main_block_impl_0中的初始化如下
{
	impl.isa = &_NSConcreteStackBlock;
	impl.Flags = 0;
	impl.FuncPtr = __main_block_func_0;
	Desc = &__main_block_desc_0_DATA;
	val = 10;
}

// 调用Block的函数时截获变量相当于
static void __main_block_func_0(struct __main_block_impl_0 *cself) 
{
	int val = cself->val;
	printf(val);
}
  • 截获变量时相当于执行Block语法时,Block语法所用的自动变量的值被保存到了Block的结构体实例里
__block说明符
  • __block的全称为:__block存储域类说明符(__block storage-class-specifier)
struct __Block_byref_val_0 {
	void *__isa;
	__block_byref_val_0 *__forwarding;
	int __flags;
	int __size;
	int val;
};

struct __main_block_impl_0 {
	// 没写出来的和上面相同
	__Block_byref_val_0 *val;
}

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
	__Block_byref_val_0 *val = __cself->val;
	(val->__forwarding->val) = 1;
}

int main()
{
	__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
	blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
	
	return 0;
}

以上源码可以总结为:

  • __block变成了结构体实例__Block_byref_val_0的结构体类型实例变量,并且被修饰的变量出现在该结构体里
  • __main_block_impl_0里持有指向__block变量的__Block_byref_val_0结构体实例指针
  • 通过__forwarding指针访问该结构体里的成员变量val
  • 在这里使用__block修饰的变量可以通过改变结构体指针改变该结构体成员变量的值
Block 的 Copy
  • Block中的isa指针是指向其Class的,在Runtime中定义了以下几种类:
  1. _NSConcreteStackBlock
  2. _NSConcreteGlobalBlock
  3. _NSConcreteMallocBlock
typedef int (^blk_t)(int);

blk_t func(int rate)
{
	return ^(int count) { return rate * count; };
}

blk_t func(int rate)
{
	blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
	
	tmp = objc_retainBlock(tmp);
	
	return objc_autoreleaseReturnValue(tmp);
} 

// objc_retainBlock函数实际上就是__Block_copy函数
{
	// 将栈上的Block复制到堆上
	// 将堆上的地址作为指针赋值给变量tmp
	tmp = __Block_copy(tmp);
	
	return objc_autoreleaseReturnValue(tmp); 
}
  • __Block变量会随着Block变量被复制到堆上,在栈上的__Block变量被Block变量使用,复制到堆上后被Block变量持有,增加__Block变量的引用计数
{
__block int val = 0;

void (^blk)(void) = [^{ ++ val; }];

++ val;

blk();
}

// 无论是
^{ ++val; }
// 还是
++val;
// 都被转为
++ (val.__forwarding->val);
  • 在变换Blcok语法的函数中,该变量val为复制到堆上的__Block变量结构体实例,而用Block无关的变量val为复制前栈上的__block结构体实例
  • 但栈上的__block变量结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值复制为目标堆上的__block变量的结构体实例地址
  • 所以无论__block结构体无论在Block语法外还是语法中使用(栈上或堆上),都能正确访问
截获对象
blk_t blk;
{
	id array = [[NSMutableArray alloc] init];
	blk = [^(id blk) {
		[array addObject: obj];
		NSLog(@"%ld", [array count]);
	} copy];
}

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0* Desc;
	id __strong array;
};

// 相当于retain
static void __main_block_copy_0(struct __main_block_impl_0 dst, struct __main_block_impl_0 *src)
{
	_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}

// 相当于release
static void _main_block_dispose(struct __main_block_impl_0 *src)
{
	_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}
  • Block从栈复制到堆上调用copy和dispose函数,
  1. 调用Block的copy实例方法时
  2. Blcok作为函数返回值返回时
  3. 将Block赋值给__strong修饰符id类型的类或Block类型成员变量时
  4. 在方法名中含有usingBlock的Cocoa框架方法或GCD的API传递Block时
Block的引用循环

使用 __weak 和 __Block可以解开引用循环,不过使用__Block会有限制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值