block的原理探索-对象类型

/ 0

block的类型

block3 种类型,可以通过调用 class 方法或者 isa 指针查看具体类型。

3 种 类型的 block 都是继承自 NSBlockNSBlock 又继承自 NSObject

MRC 环境下 block 的类型和复制效果

类型 环境 内存区域 复制效果
__NSStackBlock__(_NSConcreteStackBlock) 访问了auto变量 从栈复制到堆
__NSMallocBlock__(_NSConcreteMallocBlock) __NSStackBlock__调用了copy 引用计数增加
__NSGlobalBlock__(_NSConcreteGlobalBlock) 没有访问auto变量 程序的数据区域(.data) 什么也不做

NSStackBlock存在的问题

如果在 MRC 环境,确实是访问了 auto 变量就是 __NSStackBlock__ 类型。但是如果在 ARC 环境,变成了 __NSMallocBlock__

这是因为 ARC 自动做了很多事情,比如自动调用了 copy 方法将 __NSStackBlock__ 变为了 __NSMallocBlock__

Target -> Build Settings -> Objective-C Automatic Reference Counting 改为 NO 则是 MRC,默认是 YES

下面代码在 MRC 环境下运行,发现在 test 函数执行完毕后再执行 blockage 变量变成了垃圾数据:

void (^block)(void);
void test() {
    int age = 10;
    block = ^{
        NSLog(@"age = %d", age);
    };
    NSLog(@"block=%@", [block class]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test(); // block=__NSStackBlock__
        block(); // age = 1876947496
    }
    return 0;
}

这是因为 block 是存储在栈内存的,函数执行完毕后,数据可能就被系统回收了。为了解决这个问题,就得把 block 放到堆内存中。

稍微改进一下,block 调用 copy 方法:

void (^block)(void);
void test() {
    int age = 10;
    block = [^{
        NSLog(@"age = %d", age);
    } copy];
    NSLog(@"block=%@", [block class]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test(); // block=__NSMallocBlock__
        block(); // age = 10
    }
    return 0;
}

可以发现,__NSStackBlock__ 调用 copyblock 类型变为了 __NSMallocBlock__。也就是 block 是存储在堆内存中,函数执行完毕后,数据不会被回收,需要自己管理内存释放。

ARC下block的自动copy

ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下情况:

typedef void (^Block)(void);

Block test() {
    int age = 10;
    return ^{
        NSLog(@"age = %d", age);
    };;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;

        // 作为函数返回值时
        NSLog(@"%@", [test() class]);

        // 赋值给__strong指针时,默认就是__strong
        Block block = ^{
            NSLog(@"age = %d", age);
        };
        NSLog(@"%@", [block class]);

        // 没有赋值给__strong指针,不会copy
        NSLog(@"%@", [^{
            NSLog(@"age = %d", age);
        } class]);

        // 作为Cocoa API中方法名含有usingBlock的方法参数时
        [@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}];

        // 作为GCD API的方法参数时
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
    }
    return 0;
}

MRCblock 属性的建议写法:

@property (copy, nonatomic) void (^block)(void);

ARCblock 属性的建议写法

@property (copy, nonatomic) void (^block)(void);
@property (strong, nonatomic) void (^block)(void);