之前有篇文章是探究 block 的原理,了解了 block 的 3 种类型
最近又看到 __block
修饰对象的 __forwording
指针在不同类型的 block 对象中表现不同,因此写了一些代码进行测试,并发现我之前对于 block 3 中类型的理解是有偏颇的,因此没有很好的理解 __forwording
指针
1. Block 类型
我们先看以下代码会输出什么
- (void)viewDidLoad {
[super viewDidLoad];
void(^ block_1)(void) = ^(void) {
};
NSLog(@"block_1: %@", block_1);
int a = 0;
void(^ block_2)(void) = ^(void) {
a;
};
NSLog(@"block_2: %@", block_2);
}
我一开始以为应该会输出,因为这两个 block 不都是局部变量吗,那么应该放在栈区啊
block_1: <__NSStackBlock__: 0x...> // 错误
block_2: <__NSStackBlock__: 0x....> // 错误
那么我们看看正确答案
block_1: <__NSGlobalBlock__: 0x...> // 正确
block_2: <__NSMallocBlock__: 0x....> // 正确
那么我们分析一下为什么会这样
我们先看看 block_2 的原因,因为了解了 block_2 有助于了解 block_1
为什么 block_2 是一个堆区的 block 呢,明明没有进行栈区 block 的 copy 操作啊,那我们再看看以下代码就会有种恍然大悟的感觉。就如注释所说,其实我们那段代码并非是初始化一个 栈区 block,而是将一个 栈区block 赋值给 block_2 这个变量
。而在 ARC 中通过强引用
一个block,会将其 copy 到堆区。因此 block_2 是一个 堆区 block
int a = 0;
// 这个 block_1 才是一个 栈区的 block
void *block_1 = (__bridge void *)^(void) {
a;
}();
// 而这个表示的是将一个 栈区的block 赋值给 block_2 这个变量
void(^block_2)(void) = ^(void) {
a;
};
block();
接下来我们看看 block_1,首先我要纠正我一个错误的思想,判断一个 block 是 栈区
的还是 全局区
的一句不应该是它是否是局部变量,而是 block 中是否访问了 auto 变量
,因此上面 block_1 是 copy 了一个 全局区的 block,那么还是一个全局区的 block。因此 block_1 是一个全局区的 block。
那么我们总结 3 种类型的 block
__NSStackBlock__ | __NSGlobalBlock__ | __NSMallocBlock__ | |
---|---|---|---|
存放区域 | 栈区 | 全局区(静态区) | 堆区 |
类型说明 | block 访问了 auto 变量 | block 没有访问 auto 变量 | copy 或者 NSStackBlock 类型后生成的 block 为 NSMallocBlock类型 |
copy 操作会发生什么 | 会在堆区生成一个 NSMallocBlock 类型block | 还是一个 NSGlobalBlock | 会对当前 block 引用计数 +1 |
2. __forwording 指针
对 block 的 3 种类型了解后,就可以写测试代码看我们 __forwording
指针的原理
__block int a = 1;
printf("原始 __forwording 地址: %p\n", &a);
^(void) {
printf("栈区 __forwording 地址: %p\n", &a);
}();
这时输出以下,是一样的,说明 栈区 block 的 __block
对象的 __forwording
指针就是指向自己
原始 __forwording 地址: 0x7ffeea4c4928
栈区 __forwording 地址: 0x7ffeea4c4928
我们再看
__block int a = 1;
printf("原始 __forwording 地址: %p\n", &a);
^(void) {
printf("原始栈区 __forwording 地址: %p\n", &a);
}();
// copy block
void(^ block)(void) = ^(void) {
printf("堆区 __forwording 地址: %p\n", &a);
};
block();
^(void) {
printf("copy后栈区 __forwording 地址: %p\n", &a);
}();
这时输出以下,说明 __block
对象 a 在 copy 到堆上之后, 它的 __forwording
指针都指向堆上的 a 对象
原始 __forwording 地址: 0x7ffeea51e928
原始栈区 __forwording 地址: 0x7ffeea51e928
堆区 __forwording 地址: 0x6000002a2298
copy后栈区 __forwording 地址: 0x6000002a2298