ios Block 类型

作者: 西风那个吹呀吹 | 来源:发表于2017-06-16 12:40 被阅读1111次

    Objective-C 中 Block 有三种类型:

    NSStackBlock    存储于栈区
    NSGlobalBlock   存储于程序数据区
    NSMallocBlock   存储于堆区
    

    MRC 下

        @property (nonatomic, copy ) void(^block)();
    
        int value = 10;
        void(^blockA)() = ^{
            NSLog(@"value: %d",value);
        };
        NSLog(@"MRC 引用计数: %ld, block is: %@",[blockA retainCount], blockA);
    
        void(^blockB)() = ^{
            NSLog(@"blockB");
        };
        NSLog(@"MRC 引用计数: %ld, block is: %@",[blockB retainCount], blockB);
    
        _block = [blockA copy];
        NSLog(@"MRC 引用计数: %ld, block is: %@",[self.block retainCount],self.block);
        
        [_block retain];
        NSLog(@"MRC 引用计数: %ld, block is: %@",[self.block retainCount],self.block);
    
        [_block release];
        NSLog(@"MRC 引用计数: %ld, block is: %@",[self.block retainCount],self.block);
    

    打印结果:

        MRC 引用计数: 1, block is: <__NSStackBlock__: 0x7fff59038bc8>
        MRC 引用计数: 1, block is: <__NSGlobalBlock__: 0x106bc70e0>
        MRC 引用计数: 1, block is: <__NSMallocBlock__: 0x610000058330>
        MRC 引用计数: 1, block is: <__NSMallocBlock__: 0x610000058330>
        MRC 引用计数: 1, block is: <__NSMallocBlock__: 0x610000058330>
    
    

    可以看到,blockA 与 blockB 的差异只在于有没有调用外部变量,这点差异导致它们的类型不同,存储位置不同。

    • NSGlobalBlock
      block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。
    • NSStackBlock
      block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。
    • NSMallocBlock
      如上例中的_block[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。

    ARC 下:

        @property (nonatomic, copy ) void(^block)();
    
        int value = 10;
        void(^blockA)() = ^{
            NSLog(@"value: %d",value);
        };
        NSLog(@"ARC 引用计数: %ld, block is: %@",CFGetRetainCount(((__bridge CFTypeRef)blockA)), blockA);
        
        void(^blockB)() = ^{
            NSLog(@"blockB");
        };
        NSLog(@"ARC 引用计数: %ld, block is: %@",CFGetRetainCount(((__bridge CFTypeRef)blockB)), blockB);
        
        _block = blockA;
        NSLog(@"ARC 引用计数: %ld, block is: %@",CFGetRetainCount(((__bridge CFTypeRef)_block)), _block);
    

    打印结果:

        ARC 引用计数: 1, block is: <__NSMallocBlock__: 0x6080000536b0>
        ARC 引用计数: 1, block is: <__NSGlobalBlock__: 0x106bc7140>
        ARC 引用计数: 1, block is: <__NSMallocBlock__: 0x6080000536b0>
    

    我们发现,同样的 Block 变量 blockA 在 MRC 下是 NSStackBlock 类型,而在 ARC 下是 NSMallocBlock 类型。再来看看下面ARC下的测试代码:

       int value = 10;
        NSLog(@"%@",^{
            NSLog(@"value: %d",value);
        });
        
        void(^blockA)() = ^{
            NSLog(@"value: %d",value);
        };
        NSLog(@"ARC 引用计数: %ld, block is: %@",CFGetRetainCount(((__bridge CFTypeRef)blockA)), blockA);
    
        //打印结果
        //<__NSStackBlock__: 0x7fff592aebd8>
        //ARC 引用计数: 1, block is: <__NSMallocBlock__: 0x6180000487f0>
    
    

    由此看出,block 变量在赋值的时候系统自动将其拷贝到堆区了,造成我们看到变量 blockA 是 NSMallocBlock 类型。

    我们再定义两个 block 属性:

    @property (nonatomic, /*copy strong assign retain*/assign) void(^block)();
    @property (nonatomic, copy) void (^nameAge)();
    
    

    通过 clang 查看cpp源码

    static void(* _I_OneViewController_block(OneViewController * self, SEL _cmd) )(){ return (*(void (**)())((char *)self + OBJC_IVAR_$_OneViewController$_block)); }
    static void _I_OneViewController_setBlock_(OneViewController * self, SEL _cmd, void (*block)()) { (*(void (**)())((char *)self + OBJC_IVAR_$_OneViewController$_block)) = block; }
    
    static void(* _I_OneViewController_nameAge(OneViewController * self, SEL _cmd) )(){ return (*(void (**)())((char *)self + OBJC_IVAR_$_OneViewController$_nameAge)); }
    extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
    
    static void _I_OneViewController_setNameAge_(OneViewController * self, SEL _cmd, void (*nameAge)()) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct OneViewController, _nameAge), (id)nameAge, 0, 1); }
    

    可以看到两个属性的setter getter 方法,assign 修饰的 block 属性的setter 方法是直接赋值的,这样我们就可以理解语句_block = blockA;,局部变量 blockA 赋值给 属性 block,是等同于基本数据类型的值赋值,在其所在的作用域结束后,属性block 指向的内存会自动释放的,属性 block 就是野指针了,在方法外调用属性block,会奔溃。

    - (IBAction)tapBtnOne:(UIButton *)sender {
    
        NSLog(@"%@",_block);
    }
    

    调用方法tapBtnOne会奔溃。

    可是我们打印出变量 blockA 的类型是NSMallocBlock,其内存分配在堆区,_block = blockA;后,属性 block 指向同一份的blockA所指的内存块,按理说其分配在堆区,作用域结束也不会被系统自动释放。
    其实很好理解,属性 _block 修饰限定符是 assign,表明其任何操作不会引用计数增加的,也就是说_block = blockA;语句后,_block的引用计数没有增加并且指向的还是同一份内存,作用域结束的时候,ARC 下会自动补全 release 操作语句来释放变量blockA,所以 _block 就成了野指针了。

    所以Block属性的特性限定符一般都是 copy,因为其setter方法中会拷贝一份新的副本到堆区。

    相关文章

      网友评论

      • 子天々君:“block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型” 外部变量是指?其实全局变量 静态变量也是外部变量,但还是__NSGlobalBlock__类型。。。。
      • 悟_空:很好很强大,赞一个,不过有个错别字,奔溃

      本文标题:ios Block 类型

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