Block 之 block的类型

前言:追溯block的父类,看清其本质
void(^block)(void) = ^{
        NSLog(@"Hello world");
    };

    NSLog(@"%@",[block class]);
    NSLog(@"%@",[[block class] superclass]);
    NSLog(@"%@",[[[block class] superclass] superclass]);
    NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);

====================================================
打印结果:BlockTest[2295:101750] __NSGlobalBlock__
         BlockTest[2295:101750] __NSGlobalBlock
         BlockTest[2295:101750] NSBlock
         BlockTest[2295:101750] NSObject

前面我们也说过,block本质上就是一个OC对象。
从上面的代码打印结果,我们更可以看出,block 就是一个对象,而且追溯其父类,最终可以发现是个NSObject对象。
他有isa指针,从NSObject而来。

1、block的类型

下面我们通过一段代码来证实block的类型:
注意:要将XCode设为MRC,因为ARC下,编译器会对block进行copy,做些保护操作。

Block 之 block的类型_第1张图片
屏幕快照 2018-10-29 下午5.39.13.png

运行下面的代码,我们能够看到block有三种类型。

- (void)viewDidLoad {
    [super viewDidLoad];

    void(^block1)(void) = ^{
        NSLog(@"Hello world");
    };

    int age = 10;
    void(^block2)(void) = ^{
        NSLog(@"age is %d",age);
    };

    void(^block3)(void) = [^{
        NSLog(@"age is %d",age);
    } copy];

    NSLog(@"第一种:%@",[block1 class]);
    NSLog(@"第二种:%@",[block2 class]);
    NSLog(@"第三种:%@",[block3 class]);
}

=========================================
打印结果:
BlockTest[2814:131802] 第一种:__NSGlobalBlock__
BlockTest[2814:131802] 第二种:__NSStackBlock__
BlockTest[2814:131802] 第三种:__NSMallocBlock__

结论:block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。

Block 之 block的类型_第2张图片
屏幕快照 2018-10-29 下午3.02.32.png
  • 那这三种block类型是依据什么来确定类型的呢?看下图
    Block 之 block的类型_第3张图片
    image.png

1.对于NSGlobalBlock,我们不用太过操心。
2.但是NSStackBlock在栈上存储,而block经常在别处调用,后续调用的时候并不能保证定义的那个block还在,这种类型的block有风险。但NSStackBlock调用copy操作后就会变成NSMallocBlock类型,存放到堆上了,便没有这个风险了。
(从上面代码block2->block3也可以证实)
3、NSMallocBlock类型的block由程序员手动管理,内存安全。

  • 调用copy后,NSStackBlock转为NSMallocBlock,那其他两种block类型调用copy会怎么样呢?看下图总结
    Block 之 block的类型_第4张图片
    image.png
2、ARC下Block的自动copy操作

在上面,我们已经讲过block的copy操作了(基于MRC,ARC情况下编译器会自动帮我们做很多的操作来保护block不被释放)。
那我们再来详细分析一下。
在MRC环境下:

//在MRC环境下运行这段代码
typedef void(^MyBlock)();
    int age = 10;
     //“=”这个地方是有强指针指向block
    MyBlock block = ^{
        NSLog(@"this is a block,age = %d",age);
    };
    block();
    
    NSLog(@"block的类型 %@",[block class]);
    NSLog(@"block copy之后的类型 %@",[[block copy] class]);

==================================================
打印结果:
BlockTest[6676:445306] this is a block,age = 10
BlockTest[6676:445306] block的类型 __NSStackBlock__
BlockTest[6676:445306] block copy之后的类型 __NSMallocBlock__

在ARC环境下:

//在ARC环境下运行这段代码
typedef void(^MyBlock)();
    int age = 10;
    // “=”这个地方是有强指针指向block
    MyBlock block = ^{
        NSLog(@"this is a block,age = %d",age);
    };
    block();
    
    NSLog(@"block的类型 %@",[block class]);
    NSLog(@"block copy之后的类型 %@",[[block copy] class]);

==================================================
打印结果:
BlockTest[6735:449326] this is a block,age = 10
BlockTest[6735:449326] block的类型 __NSMallocBlock__
BlockTest[6735:449326] block copy之后的类型 __NSMallocBlock__

从上面可以看出,在ARC环境下编译器会根据情况自动将栈上的block复制到堆上。那什么情况会让ARC给block加copy操作呢?

  • 1、block作为函数返回值时
  • 2、将block赋值给__strong指针时
  • 3、block作为Cocoa API中方法名含有usingBlock的方法参数时
  • 4、block作为GCD API的方法参数时
    //情况一:block作为函数返回值时
    typedef void(^MjBlock)();
    MjBlock mjBlock()
    {
        int age = 10;
        return ^{
            NSLog(@"age = %d",age);
        };
    }
    
    
    //情况二:将block赋值给__strong指针时
    typedef void(^MyBlock)();
    int age = 10;
    //这种被强指针指向,会自动copy,打印结果为:block的类型 __NSMallocBlock__
    MyBlock block = ^{
        NSLog(@"this is a block,age = %d",age);
    };
    NSLog(@"block的类型 %@",[block class]);
    
    //这种没有被强指针指向,就不会自动copy,打印结果为:block的类型 __NSStackBlock__
    NSLog(@"block的类型 %@",[^{
        NSLog(@"this is a block,age = %d",age);
    } class]);
    
    
    //情况三:block作为Cocoa API中方法名含有usingBlock的方法参数时
    NSArray *arrar = @[@"1",@"2",@"3"];
    //系统api,block作为参数
    [arrar enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
    }];
    
    
    //情况四:block作为GCD API的方法参数时
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });

所以在ARC下,我们可以省好多事情,系统已经帮我们考虑好了,我们在写block的时候,按照规定写就好。

//MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

//ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
3、总结
  • <1>block有哪些类型?
    三种类型,
    NSGlobalBlock_:没有引用外部auto变量的、
    NSStackBlock:引用了外部auto变量的、
    NSMallocBlockNSStackBlock类型block调用copy后的。

  • <2>三种类型block调用copy操作后会怎么样?
    NSGlobalBlock_:不变,依然存在数据区、
    NSStackBlock:从栈区复制到堆区、
    NSMallocBlock:还在堆区,引用计数器增加。

你可能感兴趣的:(Block 之 block的类型)