Block类型及内存区域

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类型和存储区域的总结。中间为探究后三种类型浪费了很多时间,导致文章延迟。