Block的存储域

2019-05-08  本文已影响0人  霸_霸霸

1. StackBlock

#include <stdio.h>
  
static int a = 1;

int main() {
    static int b = 2;
    void (^blk)() = ^ {
        a *= 2;
        b *= 2;
        printf("a:%d\n",a);
        printf("b:%d\n",b);
    };
    blk();
}

2. Global Block

#include <stdio.h>

static int a = 1;
void (^globalBlock)(void) = ^{printf("a:%d\n",a);};

int main() {
    globalBlock();
}

注意:GlobalBlock是全局Block,不需要截取自动变量,整个程序只需要一个实例,所以存放在全局区中即可。
或者说:如果一个StackBlock不需要截取自动变量,那么可以看做是GlobalBlock,尽管我们通过gcc看到的是NSConcreteStackBlock

3.Malloc Block

Block创建时都是在栈区的,那么堆区的Block从何而来呢?

堆区Block是栈区Block通过copy复制到堆区的,形成了Malloc Block。

问题1:为何要形成堆Block?
因为栈Block以及栈Block使用的__block变量,如果它们所在的变量作用域结束,Block以及__block变量都会被废弃,这会导致一些意想不到的问题;所以我们引入堆Block解决这个问题

问题2:如何形成堆Block?
简单来说,就是通过copy,将Block以及__block变量复制到堆区,且让Block对象强引用__block变量,以保持__block变量不会被释放;当Block被释放时,没有对象引用的__block变量,也会被释放。

举个例子

typedef void(^BLK) (void);
//1. 创建一个方法,返回一个数组,数组里存放几个Block
- (NSArray *)getBlocks{
    int val = 10;
    return [[NSArray alloc]initWithObjects:^{NSLog(@"Block1.val:%d",val);},^{NSLog(@"Block2.val:%d",val);}, nil];
}

//2. 调用方法
- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *arr = [self getBlocks];
    BLK blk1 = [arr objectAtIndex:0];
    blk1();
}

报错:EXC_BAD_ACCESS
原因:数组里的Block是栈Block,超出作用域,被释放

解决方法:
将返回的两个Block拷贝到堆区即可

- (NSArray *)getBlocks{
    int val = 10;
    return [[NSArray alloc]initWithObjects:[^{NSLog(@"Block1.val:%d",val);} copy],[^{NSLog(@"Block2.val:%d",val);} copy], nil];
}

以上两个问题,可参考《Objective-C高级编程》108页开始的2.3.4章节。这里主要说说以下两点:

  1. __block变量的存储区域
  2. Block为何能够使变量超出变量作用域仍可使用?

2.1 __block变量的存储区域

    typedef void(^Block2)(void);
    __block int a = 2;
    Block2 block2 = [^{
        a++;
        NSLog(@"a:%d",a);
    } copy];
    a++;
    block2();

结果:a:4

上面的代码中,有两个a++的操作

  1. Block中的a++,按照我们上面的说法,a和block2已经从栈区复制到堆区了;
  2. 第二个a++,操作的还是栈区的a++,跟复制到堆区的a++无关
    那么结果不应该是3么?为什么还是4?

这就要扯到Block以及__block的底层代码了:
__block变量其实是一个C语言结构体,其中包含了int类型的值a,还有一个__forwarding指针,这个__forwarding指针,平时是指向结构体本身的;但是当__block变量从栈区复制到堆区之后,这个__forwarding指针就不再指向栈区的结构体了,而是指向堆区的__block变量的结构体,这样一来,无论我们修改栈区a的值还是堆区a的值,其实都是在修改堆区a的值(如下图)

__block变量存储区域.png

2.2 Block为何能够使变量超出变量作用域仍可使用?

    typedef void(^BLK2) (id);
    BLK2 blk2;
//----------变量作用域begin----------
    {
        NSMutableArray *array = [NSMutableArray array];
        blk2 = ^(id obj){
            [array addObject:obj];
            NSLog(@"count:%ld",weakArray.count);
        };
    }
//----------变量作用域end----------
    blk2([NSObject new]);
    blk2([NSObject new]);
    blk2([NSObject new]);

结果

count:1
count:2
count:3

很明显,上面代码中的array的作用域仅限于大括弧内,但是我们在变量作用域结束后调用blk2,仍然可以为array添加元素;这是为什么呢?
这是因为blk2结构体中,存在一个id __strong array;,该array强引可变数组array,因此变量作用域结束后,可变数组不会被释放,我们仍然可以像可变数组array中写入数据;

强引.png

那么如何使可变数组array不被block结构体中的array强引呢?

很简单,使block中的strong array强引一个weakArray即可

typedef void(^BLK2) (id);
    BLK2 blk2;
    {
        NSMutableArray *array = [NSMutableArray array];
        __weak typeof(array) weakArray = array;
        blk2 = ^(id obj){
            [weakArray addObject:obj];
            NSLog(@"count:%ld",weakArray.count);
        };
    }
    blk2([NSObject new]);
    blk2([NSObject new]);
    blk2([NSObject new]);

结果

count:0
count:0
count:0
弱引.png
上一篇下一篇

猜你喜欢

热点阅读