美文网首页
Block学习总结(三)

Block学习总结(三)

作者: cr7aa | 来源:发表于2017-02-07 22:33 被阅读0次

    关于block的存储域

    一、 block变量存储域

    1. ARC和MRC不同的存储情况

    通过对block本质的探究,发现block内部也是有一个isa指针指向它所属的类,所以block其实也是一个对象。

    // ARC 环境下
        void (^blk)() = ^{
        
        };
        NSLog(@"%@",[blk class]);
    
    

    其打印结果为 " _ _NSGlobalBlock _ _ ",可想而知block是属于一个叫NSGlobalBlock类的对象。其实 block的类有三种。分别为 NSGlobalBlock ,NSStackBlock ,NSMallocBlock。从它们的名字能猜测到,这三种类型的block对应着block不同的存储区域。相继为 global(全局数据区域) stack (栈区) malloc(堆区)。

    分别在ARC和MRC的情况下看看哪些情况下对应的block存储区域,首先是ARC的情况下

        NSLog(@"未赋值 未引用外部变量 %@", [^void() {
        
        } class]);
        
        void (^blk)() = ^{
        
        };
        NSLog(@"赋值 未引用外部变量 %@",[blk class]);
        
        int a = 1;
        void (^blk1)() = ^{
            a;
        };
        NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
        
        NSLog(@"未赋值 引用外部变量  %@", [^void() {
            a;
        } class]);
    
    

    最后的输出结果为:

    2017-01-08 21:49:15.389 block存储域[10956:1104789] 未赋值 未引用外部变量 __NSGlobalBlock__
    2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 未引用外部变量 __NSGlobalBlock__
    2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 引用外部变量 __NSMallocBlock__
    2017-01-08 21:49:15.390 block存储域[10956:1104789] 未赋值 引用外部变量  __NSStackBlock__
    
    

    可以对ARC的情况下做一个总结:

    • NSGlobalBlock === 在block未引用外部变量的时候
    • NSMallocBlock === 在block引用了外部变量的时候,且将block赋值给了block类型的变量
    • NSStackBlock === 在block引用了外部变量的时候, 没有将block赋值给block类型的变量时。

    接下来是对在MRC的情况下,block的不同存储区域

        NSLog(@"未赋值 未引用外部变量 %@", [^void() {
        
        } class]);
        
        void (^blk)() = ^{
        
        };
        NSLog(@"赋值 未引用外部变量 %@",[blk class]);
        
        int a = 1;
        void (^blk1)() = ^{
            a;
        };
        NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
        
        NSLog(@"未赋值 引用外部变量  %@", [^void() {
            a;
        } class]);
    

    输出结果为:

    2017-01-08 21:56:21.789 block存储域[10994:1108230] 未赋值 未引用外部变量 __NSGlobalBlock__
    2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 未引用外部变量 __NSGlobalBlock__
    2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 引用外部变量 __NSStackBlock__
    2017-01-08 21:56:21.791 block存储域[10994:1108230] 未赋值 引用外部变量  __NSStackBlock__
    
    

    我们可以看到无论是否引用外部变量和是否赋值给了block类型的变量,都没有出现mallocBlock,那么MRC的情况下如何才会生成mallocBlock呢。这时我们需要调用对象的copy方法。

        void (^blk)() = ^{
        
        };
        NSLog(@"copy 未引用外部变量 %@",[[blk copy] class]);
       
        int a = 1;
        void (^blk1)() = ^{
            a;
        };
        NSLog(@"copy 引用外部变量 %@",[[blk1 copy] class]);
    

    2. ARC情况下什么时候会进行copy

    block引用了外部变量就会存储在栈区,在ARC环境下大多数情况会自动copy操作后复制到堆区。但是有些情况下编译器不会自动进行copy操作:

    • block作为函数参数的时候不会自动进行copy操作,除了以下情况之外:

      (1) Cocoa框架的方法切方法名中又usingBlock。

      (2) GCD的API。

    通过以下代码验证:

      - (void)viewDidLoad {
        [super viewDidLoad];
        NSArray *tempArray = [self getBlockArray];
        blk_t blk = tempArray[0];
        blk();
    }
    
    - (id)getBlockArray {
        int a = 10;
        return [[NSArray alloc] initWithObjects:^{
            NSLog(@"blk1 %d",a);
        },^{
            NSLog(@"blk2 %d",a);
                }, nil];
    }
    
    

    因为作为参数传递的block的存储区域为栈区,而出了 getBlockArray 函数后,block的作用域结束了,当调用函数取得数组时,访问已经释放了的block,就会造成了crash。要是想正确访问block ,可以做以下修改,对作为参数传递的block进行copy操作。

    - (id)getBlockArray {
        int a = 10;
        return [[NSArray alloc] initWithObjects:[^{
            NSLog(@"blk1 %d",a);
        } copy],[^{
            NSLog(@"blk2 %d",a);
                }copy], nil];
    }
    

    二、 __block变量存储域

    我们知道对于block内部使用的外部变量,不允许在block内部修改外部变量的值。以下代码是编译不过的。

        int a = 10;
        void (^blk)() = ^(){
        a = 20;
        } ;
        blk();
    

    因为在block进行copy的同时,会将block内使用的外部变量也进行一次copy操作,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。

        int a = 10;
        NSLog(@"定义block前 %p",&a);
        void (^blk)() = ^(){
            NSLog(@"block内部 %p",&a);
        } ;
        blk();
        NSLog(@"定义block后 %p",&a);
    

    打印结果为:

    2017-02-08 14:20:06.716 block存储域_test[23117:147534] 定义block前 0x7fff5b635a4c
    2017-02-08 14:20:06.717 block存储域_test[23117:147534] block内部 0x60800005a1b0
    2017-02-08 14:20:06.717 block存储域_test[23117:147534] 定义block后 0x7fff5b635a4c
    

    16进制转换为10进制,block外部为 140734726625868 ,block内部使用的时候为 106102872449456,2者相差1532868764个字节,转换为1461 mb。因为堆地址远小于栈中的地址,又因为iOS中一个进程的栈区内存只有 1mb,所以在block内部 的变量已经在堆区内了。

    __block 变量

    对于block内部使用 _ _block变量时,和普通的变量一样,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。但是为什么能在block内部修改 _ _block修饰的变量。

        __block int a = 10;
        NSLog(@"定义block前 %p",&a);
        void (^blk)() = ^(){
            a++;
            NSLog(@"block内部 %p",&a);
        } ;
        NSLog(@"定义block后 %p",&a);
        blk();
    

    打印的结果为:

    2017-02-08 14:44:01.933 block存储域_test[26207:167214] 定义block前 0x7fff569eca48
    2017-02-08 14:44:01.934 block存储域_test[26207:167214] 定义block后 0x60800003eb38
    2017-02-08 14:44:01.934 block存储域_test[26207:167214] block内部 0x60800003eb38
    

    与普通的外部变量不同的是,定义block后,变量的地址也指向了堆中的那个地址。而不是定义之前的栈中地址。还记得__block内部那个指向 变量自身的那个forwading指针吗,当 _ _block变量赋值到堆区时,栈区的变量的forwading指针也改为了指向堆中的那个变量的地址,所以在block外部使用变量时,它访问的是堆中的那个变量

    a++  <===> a._forwarding ->a)++
    

    三、 截获的对象存储域

    通常的一个变量,如下,当它超过了作用域之后,就会被释放了。

    - (void)test5 {
        NSMutableArray *tempArray = [NSMutableArray array];
    }
    

    但是对于block中使用的外部变量,在某些情况下,却能超过变量的作用域存在。

    (该代码 是在ARC环境下运行)
    blk_t globalblk;
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        globalblk();
    }
    
    - (void)test5 {
        NSMutableArray *tempArray = [NSMutableArray array];
        globalblk = ^{
            id obj = [[NSObject alloc] init];
            [tempArray addObject:obj];
            NSLog(@"count == %ld a == %d", tempArray.count,a);
        };
    }
    

    该代码中,tempArray变量出了 test5这个函数,就超出了作用域,但是该代码却能正确的打印出来。说明: 在ARC环境下,当给block赋值给一个block变量时,将会默认的对block进行一次copy操作。如果在MRC环境下,也就是没有对block进行手动copy的话,这段代码就会crash。

    四、 __block和对象

    关于__block修饰的对象,被block使用的时候,在ARC和MRC情况下,是有很大的不同的。当在ARC环境下,block内部使用 _ _block修饰的对象的时候,它和正常的外部变量一样,当block从栈区copy到堆区时,会被block所持有,因此能超出变量总用域存在。而在MRC的情况下,当 _ _block修饰的变量被block使用时,不会随着block从栈区copy到堆区而被block所持有。

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        globalblk();
    }
    
    - (void)test5 {
        __block NSMutableArray *tempArray = [NSMutableArray array];
        globalblk = [^{
            id obj = [[NSObject alloc] init];
            [tempArray addObject:obj];
            NSLog(@"count == %ld", [tempArray count]);
        } copy];
    }
    

    以上的代码,在ARC环境下能正常运行,在MRC情况下会因为野指针的访问而crash。

    五、 block的循环引用

    block持有_ _strong修饰的外部变量时,block会持有该对象,而如果该对象又持有block时,就会造成循环引用的问题。

    typedef void (^blk_t)();
    @interface Person ()
    {
        blk_t blk;
    }
    @end
    
    @implementation Person
    - (instancetype)init {
        if (self = [super init]) {
            blk = [^(){
                NSLog(@"%@",self);
            } copy];
        }
        return self;
    }
    
    - (void)dealloc {
        NSLog(@"dealloc");
    }
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *p = [[Person alloc] init];
    }
    

    该段代码的dealloc一定不会被打印,因为在调用 Person的init函数是,成员变量blk使用了 self,使得blk对 self 持有。而blk作为 Person对象的成员变量,Person对象就对 blk持有。造成了这种互相持有的关系后,该对象就无法释放了。

    循环引用问题.png

    解决方法(1): 通过对block内部使用的变量,改为weak修饰。

    - (instancetype)init {
        if (self = [super init]) {
            __weak typeof(self) weakSelf = self;
            blk = [^(){
                NSLog(@"%@",weakSelf);
            } copy];
        }
        return self;
    }
    

    解决方法(2): 之前说过,在MRC环境下,用_ _block修饰变量时,block不会对该变量进行持有。

    //仅对MRC环境下有效
    - (instancetype)init {
        if (self = [super init]) {
            __block tempSelf = self;
            blk = [^(){
                NSLog(@"%@",tempSelf);
            } copy];
        }
        return self;
    }
    

    相关文章

      网友评论

          本文标题:Block学习总结(三)

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