IOS 拾遗程序员iOS Developer

(二)Block之存储域 NSConcreteStackBloc

2017-10-15  本文已影响109人  madaoCN

相关文章


我们已经知道,Block将被转换为Block的结构体的自动变量, 即栈上生成的结构体实例。而且当Block被当做OC对象来看时,Block的isa指针为_NSConcreteStackBlock

导读

本文以下内容我们将探究包括_NSConcreteStackBlock的与之相似的几个类:

_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

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作为返回值时,编译器会自动将变量拷贝至堆,有时候编译器无法判断,需要手动调用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 引用计数增加
上一篇下一篇

猜你喜欢

热点阅读