美文网首页IOS 拾遗程序员iOS Developer
(二)Block之存储域 NSConcreteStackBloc

(二)Block之存储域 NSConcreteStackBloc

作者: madaoCN | 来源:发表于2017-10-15 23:20 被阅读109次

    相关文章


    我们已经知道,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 引用计数增加

    相关文章

      网友评论

        本文标题:(二)Block之存储域 NSConcreteStackBloc

        本文链接:https://www.haomeiwen.com/subject/qoequxtx.html