ios Block 类型

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方法中会拷贝一份新的副本到堆区。



转自:https://www.jianshu.com/p/6568f245deb2
 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值