(二)Block之存储域 NSConcreteStackBloc
相关文章
- (一)Block的实质初探
- (二)Block之存储域 NSConcreteStackBlock,NSConcreteGlobalBlock,NSConcreteMallocBlock
- (三)Block之截获变量和对象
- (四)Block之 __block修饰符及其存储域
我们已经知道,Block将被转换为Block的结构体的自动变量, 即栈上生成的结构体实例。而且当Block被当做OC对象来看时,Block的isa指针为_NSConcreteStackBlock
导读
本文以下内容我们将探究包括_NSConcreteStackBlock
的与之相似的几个类:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
_NSConcreteStackBlock
中包stack
关键字,我们可以推测它设置在栈上(编译器分配内存)
_NSConcreteGlobalBlock
中包Global
关键字,我们可以推测它设置在数据区域
_NSConcreteMallocBlock
中包Malloc
关键字,我们可以推测它设置在堆上(由程序员分配内存)
那么在分配内存上对应区域:
程序内存分配.png类 | 设置对象的存储于 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 数据区 |
_NSConcreteMallocBlock | 堆 |
_NSConcreteStackBlock
int i = 0;
void (^blk)() = ^() {
printf("%d", i);
};
blk();
局部定义的Block 经过代码转换后
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; //设置在栈区的Block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
大多数时候,clang转换的源代码通常是_NSConcreteStackBlock
对象
_NSConcreteGlobalBlock
void (^blk) () = ^{
printf("Block");
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
blk();
}
return 0;
}
全局定义的Block经过代码转换后
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock; //设置在数据区的Block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
当以下两种情况时,Block被转换为_NSConcreteGlobalBlock
- 全局变量有Block语法的时候
全局变量的地方不能使用自动变量,不存在对自动变量进行捕获的情况,所以Block不依赖执行时候的状态,这时候Block设置为_NSConcreteGlobalBlock
- Block语法不使用截获的自动变量时候
int(^blk)(int count) = ^(int count) {
return count;
};
示例代码如上,Block并不需要捕获自动变量,也可以将Block设置在数据区域
_NSConcreteMallocBlock
对于以下代码
typedef int (^Block)(int num);
Block foo(){
int i = 0;
return ^{printf("blk:%d", i);};
}
将如上代码转换为汇编输出(如何获取汇编代码输出请看 iOS 获取汇编输出方法 以下采用整理过后的伪代码形式)
Block foo(){
int i = 0;
Block tmp = &__foo_block_impl_0(
&__foo_block_func_0,
&__foo_block_desc_0_DATA,
i));
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
如下为objc_retainBlock
的源码
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
很显然调用了objc_retainBlock
就等于调用了_Block_copy
, 作用是将Block拷贝至堆。
那么,当以下情况时,Block被拷贝至堆,那么Block也将配置为_NSConcreteMallocBlock
- Block作为返回值,超出了变量作用域
补充
Block作为返回值时,编译器会自动将变量拷贝至堆,有时候编译器无法判断,需要手动调用copy方法,将Block拷贝至堆
typedef void (^Block)();
id foo(){
int i = 0;
return [[NSArray alloc] initWithObjects:
^{printf("blk:%d", i);},
nil];
}
NSArray *arr = foo();
Block blk = (Block)[arr firstObject];
blk();//此处执行报错
该段代码执行将会报错,因为foo()
执行结束后,栈上的Block就被释放了,所以需要手动copy 下Block
id foo(){
int i = 0;
return [[NSArray alloc] initWithObjects:
[^{printf("blk:%d", i);} copy],//手动copy block到堆
nil];
}
最后附上各类Block Copy后执行动作
类 | 设置对象的存储于 | Copy动作执行后 |
---|---|---|
_NSConcreteStackBlock | 栈 | 栈复制到堆 |
_NSConcreteGlobalBlock | 数据区 | 不做变化 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |