iOS开发那些事iOS开发攻城狮

ios Block 类型

2017-06-16  本文已影响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 的差异只在于有没有调用外部变量,这点差异导致它们的类型不同,存储位置不同。

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

上一篇下一篇

猜你喜欢

热点阅读