Block

什么是 Block?

Block 是将函数及其执行上下文封装起来的对象(结构体内含有 isa 指针)。比如:

void blockFunc2() {
	__block int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

对应C语言的实现:

void blockFunc2() {
	__attribute__ __Block_byref_num_0 num = {0, &num, 0, sizeof(__Block_byref_num_0 ), 100};

	//通过指针运算符"&"获取地址,并赋值给block函数(构造函数并没有返回值,使用"&"得到的是什么地址???)
	void (*block)() = &__blockFunc2_block_impl_0(__blockFunc2_block_func_0, 
												 &__blockFunc2_block_desc_0_DATA, 
												 &num, 
												 570425344);
												 
	(num.__forwarding->num) = 200;

	((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
其中:

__blockFunc2_block_impl_0() 是结构体 __blockFunc2_block_impl_0 的构造函数

struct __blockFunc2_block_impl_0 {
	struct __block_impl impl;
	struct __blockFunc2_block_desc_0 *Desc;
	__Block_byref_num_0 *num;

	__blockFunc2_block_impl_0(void *fp,
							  struct __blockFunc2_block_desc_0 *desc,
							  __Block_byref_num_0 *_num,
							  int flags=0) : num(_num -> __forwarding) //初始化列表 {  
		impl.isa = &_NSConcreteStackBlock;
		impl.Flags = flags;
		impl.FuncPtr = fp;
		Desc = desc;
	}
}

__blockFunc2_block_func_0block 内部代码对应生成的函数

static void __blockFunc2_block_func_0(struct __blockFunc2_block_impl_0 *__cself) {
	__Block_byref_num_0 *num = __cself -> num;

	NSLog((NSString *)&__NSConstantStringImpl__var_folders_1w_8y5tf7_14hd5sk7_cm5sygr00000gn_T_main_ec62c2_mi_0, 
		  (num -> __forwarding -> num));
}

__block_impl 为结构体

struct __block_impl {
	void *isa;	//isa指针,所以说block是对象
	int Flags;
	int Reserved;
	void *FuncPtr;	//函数指针
}

Block的变量截获

局部变量

局部变量截获是值截获,比如:

NSInteger num = 3;
NSInteger (^block)(NSInteger) = ^NSInteger(NSInteger n) {
	return n * num;
};
num = 1;
NSLog(@"%zd", block(2));

这里的结果是6,而不是2,原因就是对局部变量 num 的截获是值截获。同样的,在 block 里修改变量 num 也是无效的,甚至编译器会报错。

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
void (^block)(void) = ^{
	NSLog(@"%@", arr);	//局部变量
	[arr addObject:@"4"];
};
[arr addObject:@"3"];
arr = nil;
block();

这里的结果为1,2,3arr 存储的是 NSMutableArray 对象的地址,将 arr 的值置为 nilblock 没有影响。

局部静态变量

局部静态变量截获是指针截获,比如:

static NSInteger num = 3;
NSInteger (^block)(NSInteger) = ^NSInteger(NSInteger n) {
	return n * num;
}
num = 1;
NSLog(@"%zd", block(2));

结果为2,代码 num = 1; 是有效的,即是指针截获。同样的,在 block 里去修改变量 num 也是有效的。

全局变量和全局静态变量

Block不截获全局变量和全局静态变量,而是直接取值

NSInteger num3 = 300;
static NSInteger num4 = 3000;

-(void)blockTest {
	NSInteger num = 30;
	static NSInteger num2 = 3;
	__block NSInteger num5 = 30000;

	void (^block)(void) = ^{
		NSLog(@"%zd", num);  //局部变量
		NSLog(@"%zd", num2);  //局部静态变量
		NSLog(@"%zd", num3);  //全局变量
		NSLog(@"%zd", num4);  //全局静态变量
		NSLog(@"%zd", num5);  //__block修饰的变量
	};
	
	block();
}

转换成C语言后:

struct __WYTest__blockTest_block_impl_0 {
	struct __block_impl impl;
	struct __WYTest__blockTest_block_desc_0 *Desc;
	NSInteger num;  //局部变量
	NSInteger *num2; //局部静态变量
	__Block_byref_num5_0 *num5;  //__block修饰的变量
	
	__WYTest__blockTest_block_impl_0(void *fp, 
									 struct __
WYTest__blockTest_block_desc_0 *desc, 
									 NSInteger _num, 
									 NSInteger 
*_num2, 
									 __Block_byref_num5_0 *_num5, 
									 int flags=0) : num(_num),num2(_num2), num5(_num5 -> __forwarding) {

		impl.isa = &_NSConcreteStackBlock;//这是个栈block
		impl.Flags = flags;
		
impl.FuncPtr = fp;
		

Desc = desc;
	}
};

另外,block 里访问 self 或成员变量都会去截获 self

__block的实现

__block 修饰的变量会被转换成结构体,比如上面代码中的:

__block int num = 100;

会被转换成

__attribute__ __Block_byref_num_0 num = {0, &num, 0, sizeof(__Block_byref_num_0 ), 100};

其中 __Block_byref_num_0 结构体的结构为:

struct __Block_byref_num_0 {
	void *__isa;  // isa指针
	__Block_byref_num_0 *__forwarding;  // 实例本身
	int __flags; 
	int __size;
	int num;  // 我们的num值,该成员由代码决定
};

为什么需要多一个__forwarding,而不是直接获取结构体内的值呢???下文讲解

如果之后执行了以下代码:

num = 200;

则相当于为其成员num赋值:

(num.__forwarding->num) = 200;

Block的几种形式

Block分为全局Block(_NSConcreteGlobalBlock)栈Block(_NSConcreteStackBlock)堆Block(_NSConcreteMallocBlock)三种形式,其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

  1. 不使用外部变量的 block 是全局Block
NSLog(@"%@", [^{
	NSLog(@"globalBlock");
} class]);

输出:

__NSGlobalBlock__
  1. 使用外部变量并且未进行 copy 操作的 block 是栈Block
NSInteger num = 10;
NSLog(@"%@", [^{
	NSLog(@"stackBlock:%zd", num);
} class]);

输出:

__NSStackBlock__

日常开发常用于这种情况:

[self testWithBlock:^{
	NSLog(@"%@", self);
}];

-(void)testWithBlock:(dispatch_block_t)block {
	block();
	NSLog(@"%@", [block class]);
}
  1. 对栈Block进行 copy 操作,就是堆Block;而对全局Block进行 copy ,仍是全局Block
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
	NSLog(@"stackBlock:%zd", num);
};  //编译器自动进行copy
NSLog(@"%@", [mallocBlock class]);

输出:

__NSMallocBlock
  • 如果对栈Block进行 copy,将会copy到堆区;如果对堆Block进行 copy,将会增加引用计数。
  • 注意循环引用

另外,__block 变量在 copy 时,由于 __forwarding 的存在,栈上的 __forwarding 指针会指向堆上的 __forwarding 变量,而堆上的 __forwarding 指针指向其自身。所以,如果对 __block 的修改,实际上是在修改堆上的 __block 变量。
__forwarding 指针存在的意义是无论在任何内存位置,都可以顺利访问同一个 __block 变量。

内存管理细节???

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值