前言:追溯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有三种类型。
- (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类型是依据什么来确定类型的呢?看下图
1.对于NSGlobalBlock,我们不用太过操心。
2.但是NSStackBlock在栈上存储,而block经常在别处调用,后续调用的时候并不能保证定义的那个block还在,这种类型的block有风险。但NSStackBlock调用copy操作后就会变成NSMallocBlock类型,存放到堆上了,便没有这个风险了。
(从上面代码block2->block3也可以证实)
3、NSMallocBlock类型的block由程序员手动管理,内存安全。
- 调用copy后,NSStackBlock转为NSMallocBlock,那其他两种block类型调用copy会怎么样呢?看下图总结
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变量的、
NSMallocBlock:NSStackBlock类型block调用copy后的。<2>三种类型block调用copy操作后会怎么样?
NSGlobalBlock_:不变,依然存在数据区、
NSStackBlock:从栈区复制到堆区、
NSMallocBlock:还在堆区,引用计数器增加。