美文网首页
Block类型及存储区域

Block类型及存储区域

作者: Angelia_滢 | 来源:发表于2018-05-07 18:58 被阅读28次

    序言

    Block前言中,讲到Block 的isa指针六种类型,以及每种类型的存储区域。简单回顾一下最终结论

    类型 查看源 存储区域
    _NSConcreteGlobalBlock .cpp文件/Block.h 全局变量/静态变量区
    _NSConcreteStackBlock .cpp文件/Block.h 栈区
    _NSConcreteMallocBlock Block_private.h文件 堆区
    _NSConcreteAutoBlock Block_private.h文件 堆区
    _NSConcreteFinalizingBlock Block_private.h文件 堆区
    _NSConcreteWeakBlockVariable Block_private.h文件 堆区

    以上内容的查看在编译后的cpp文件以及runtime源码中都可以查看到相关信息
    code已上传到Github,点击下载
    runtime源码Apple官网
    runtime源码Github

    特别注意点

    Block前言中讲到默认创建的Block指针只有Global、Statck,其它四种是在运行时编译环境决定的,此根据也是根据runtime源码和注释得出结论。

    // the raw data space for runtime classes for blocks
    // class+meta used for stack, malloc, and collectable based blocks
    BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    

    从以上runtime源码和注释中我们可以很好的解释该6种Block在Mac和iOS系统中都会出现,除Global、Statck,其它四种是在运行时编译环境决定。

    每种Block出现的情况

    (最好复习一下基本数据结构)

    _NSConcreteGlobalBlock

    在以下情况下,Block为GlobalBlock (根据赋值情况会决定存储静态区的静态变量区域还是全局变量区域)
    1.Block声明为全局变量
    2.函数区域内Block语法表达式没有使用外部变量
    3.Block内部只引用了内部传递值或只引用了静态变量或全局变量

    第一种情况好理解,一般我们声明的全局变量都会放在静态区,当Block被声明为全局变量时也会存放在静态区域
    如下将Block声明为全局变量,查看编译后的cpp文件,找到对应的编译代码
    源码:

    #import "BlockObject.h"
    #import <objc/runtime.h>
    
    void (^globalBlock)(void)=^{};
    
    @interface BlockObject()
    

    编译后的部分代码:

    struct __globalBlock_block_impl_0 {
      struct __block_impl impl;
      struct __globalBlock_block_desc_0* Desc;
      __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    `impl.isa = &_NSConcreteGlobalBlock;可以看到我们声明的全局变量block为NSConcreteGlobalBlock

    第二种情况我们可以看如下代码

     typedef int (^blockStatic)(void);
    blockStatic secondBlk = ^(){
            return 1;
     };
    NSLog(@"%@",secondBlk);
    

    最后查看到输出结果如下

    YAObjectTest[8834:1196308]
     <__NSGlobalBlock__: 0x10de381c8>
    

    所以,未引用外部变量时,最终得到的block也是NSConcreteGlobalBlock

    第三种情况我看可以看如下代码

    static int count = 100;
    typedef int (^blockStatic)(void);
    blockStatic blk = ^(){
            return count;
        };
         NSLog(@"%@",blk);
    

    最后的输出结果如下:

    YAObjectTest[8719:1185296]
     <__NSGlobalBlock__: 0x108320188>
    

    最后的输出也是GlobalBlock,所以,只引用静态变量或全局变量时,Block仍为NSConcreteGlobalBlock


    解惑:第二种情况和第三种情况我们最终查看的是输出情况,而不是clang后的源文件。查看clang后的源文件该两种情况得到的是StackBlock。编译器根据编译特性会把后两种情况默认编译后定义为StackBlock,但是我们知道OC是动态语言,所以最终还是以运行时或最终输出结果为准。


    _NSConcreteStackBlock

    1.使用到外部局部变量、成员属性变量(非静态变量值)& 2.未使用strong或copy修饰符修饰
    以上条件缺一不可,只有这两个条件同时成立时,Block才为_NSConcreteStackBlock
    如果在函数内,Block只是引用了外部局部变量或成员属性变量,最终会被copy到堆上变为MallocBlock。而对于声明为属性的Block,如果修饰符为strong或copy,则也会copy到堆上,而不是StackBlock。
    查看以下代码的最终输出(不能同时满足上述两种条件的情况下)

    @interface BlockObject()
    
    @property (nonatomic, strong)void(^proBlock)(void);
    @property (nonatomic, assign)NSInteger outsideCount;
    
    @end
    
         int a = 1;
        //使用外部局部变量情况
        void (^blockVariable)(void) = ^(){
            NSLog(@"%ld",(long)_outsideCount);
        };
        NSLog(@"%@",blockVariable);
        
        _proBlock = ^(){
            NSLog(@"%d",a);
        };
        NSLog(@"%@",_proBlock);
    

    最终控制台输出如下

     <__NSMallocBlock__: 0x600000447c50>
     <__NSMallocBlock__: 0x6040002557b0>
    
    

    将proBlock的修饰符换成weak,

    @property (nonatomic, weak)void(^proBlock)(void);
    

    同样的代码,会得到如下结果,proBlock会是StackBlock

    YAObjectTest[10461:1384618] <__NSMallocBlock__: 0x60400025d1f0>
    YAObjectTest[10461:1384618] <__NSStackBlock__: 0x7ffee9f07930>
    

    所以,在引用了外部变量并且没有强指针引用的情况下的Block为_NSConcreteStackBlock

    _NSConcreteMallocBlock

    1.引用了外部变量,有strong或copy修饰符修饰
    2.block内部需要修改引用变量的值,外部变量被__block修饰
    第一种情况在求证StackBlock的情况下已经得到验证,有strong或copy修饰后并且引用了外部变量的情况下,为** <NSMallocBlock: 0x6040002557b0>**
    有关第二种情况,在该文只做验证,后续深入解析,详见Block截获变量中的__block修饰符深入解析

        __block int a = 0;
        void (^lockBlock)(void) = ^{
            a++;
        };
        lockBlock();
        NSLog(@"%@", lockBlock);
    

    最后的输出结果为

    YAObjectTest[10853:1425358] <__NSMallocBlock__: 0x60000024e610>
    

    在此就求证了以上两种结果最后Block为_NSConcreteMallocBlock。


    接下来的三种类型Block,因对有GC回收机制的语言不是太熟悉,在Xcode中编写的C++代码 最终查看运行时的isa指针皆为—NSStackBlock,在Xcode中并没有按照预想的查看到以下三种类型,Terminal终端g++ 编译后的可执行文件也未见以下类型,查阅资料发现以下三种出现的情况大致如下,若有对C++ 熟悉的人员 ,请不吝赐教😉


    _NSConcreteFinalizingBlock

    .Block需要copy到堆上,但是Block内部有ctors 和 dtors时,block会是NSFinalizingBlock

    _NSConcreteAutoBlock

    .Block需要copy到堆上若未引用到ctors和 dtors 则是NSAutoBlock
    以上是AutoBlock和FinalizingBlock的出现情况。而对于ctors和dtors,
    ctors中保存着程序全部构造函数的指针数组,dtors中保存着程序全部析构函数的指针数组,从两者的存储内容来看,若block引用了该两种类型,势必block会在程序运行结束时回收内存,所以会被转换为FinalizingBlock,而未引用的block则会根据情况自动回收,转换为AutoBlock。

    以下是写在cpp文件中的代码

    void testAutoBlock(){
      int * b=new int[4];
       __block int testCount = 100;
       int (^myBlock)() = ^() {
            b[0] = testCount;
            b[1] = testCount + 1;
            b[2] = testCount + 2;
            b[3] = testCount + 3;
            std::cout<<b<<std::endl;
           return 0;
        };
        std::cout<<myBlock<<std::endl;
       };
    

    _NSConcreteWeakBlockVariable

    .GC回收机制下,用_weak 或__block修饰的block 会转变成NSWeakBlockVariable
    具体的对于__weak 和__blcok修饰符,在后面截获变量和循环引用中具体详解。
    以上是个人对于Block类型和存储区域的总结。中间为探究后三种类型浪费了很多时间,导致总结文章延迟。


    mine_wxpay@2x.jpeg

    倘若觉得文章还可以,各位大神给支雪糕吧!

    相关文章

      网友评论

          本文标题:Block类型及存储区域

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