名称 | 存储区域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序数据区域 .data |
_NSConcreteMallocBlock | 堆 |
_NSConcreteStackBlock
_NSConcreteGlobalBlock
_NSConcreteMallocBlock
作为全局变量时,通常我们会将 Block 定义在函数外的地方,就成了全局变量,如
void (^blk)(void) = ^{};
int main() {
return 0;
}
当我们需要在一个函数外使用到 Block 时,就需要将 Block 从栈上复制到堆上,避免当函数作用域结束后,位于栈上的 Block 失效的情况
对于 ARC 而言,多数情况下,编译器会自动判断是否需要将 Block 从栈上复制到堆上,如,当一个函数返回一个 Block 时,编译器会自动将这个函数上的 Block 复制到堆上(编译器自动添加复制代码)
涉及到的函数有
objc_retainBlock
objc_autoreleaseReturnValue
_Block_copy
例子
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count){return rate * count;}; // 编译器添加代码,将这个 Block 复制到堆上
}
自动复制的情况
usingBlock
enumerateObjectsUsingBlock
dispatch_async
手动复制,只需要在 Block 上调用 copy
方法
- (id)getBlockArray {
return [[NSArray alloc] initWithObjects:
^{},
^{},
nil,
];
}
上面的方法中,创建一个方法,这个方法返回一个数组,数组中包含了多个 Block. 然而,当执行这个方法后,在结果中取出某个 Block 时,就会发生异常出错
这是由于 Block 首先是会在函数的栈上生成,但在函数结束时,Block 没有从函数的栈上复制到堆上,被废弃了,导致从结果取出元素时发生异常
编译器在这种情况下,并不能判断是否需要将 Block 从栈上复制到堆上,因为将 Block 从栈到堆的复制操作是非常浪费 CPU 资源的。
此时,需要我们进行手动复制
- (id)getBlockArray {
return [[NSArray alloc] initWithObjects:
[^{} copy],
[^{} copy],
nil,
];
}
对于已经在堆上的 Block, 进行重复的复制操作结果
Block 类型 | Block 原本的位置 | 结果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序数据区域 .data | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
简单说:
一般来说,block 定义在某个作用内,此时,block 分配到栈上,而变量也会分配到栈上。而 block 使用到的变量值,只是变量的一个副本,因此可以读取到。
而当调用 block 去修改变量值时,由于已经逃出了定义变量的作用域,所以无法修改到变量值。而使用 __block
修饰符后,block 将会使用指针引用这个变量