美文网首页
Block的存储域

Block的存储域

作者: 霸_霸霸 | 来源:发表于2019-05-08 15:27 被阅读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变量的存储区域

    • Block创建时是存放在栈区的,__block修饰的变量也是在栈区的
    • Block经过copy,被复制到堆区之后,Block需要使用的__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

    相关文章

      网友评论

          本文标题:Block的存储域

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