前言
block对象是什么类型的,通过源码可以知道block中的isa指针指向的是_NSConcreteStackBlock类对象地址。那么block是否就是_NSConcreteStackBlock类型的呢?我们继续往下看
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
return 0;
}
///打印结果:
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
///继承链
__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
从上述打印内容可以看出block最终都是继承自NSBlock类型,而NSBlock继承于NSObjcet。那么block其中的isa指针其实是来自NSObject中的。这也更加印证了block的本质其实就是OC对象。
Block三种类型
分别为:
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
通过代码查看一下block在什么情况下其类型会各不相同
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 内部没有调用外部变量的block
void (^block1)(void) = ^{
NSLog(@"1");
};
// 2. 内部调用外部变量的block
int a = 10;
void (^block2)(void) = ^{
NSLog(@"2 - %d",a);
};
// 3. 直接调用的block的class
NSLog(@"1.%@ 2.%@ 3.%@", [block1 class], [block2 class], [^{
NSLog(@"AAA %d",a);
} class]);
}
return 0;
}
///打印
1.__NSGlobalBlock__ 2.__NSMallocBlock__ 3.__NSStackBlock__
Block在内存中的存储
通过下面一张图看一下不同block的存放区域
上图中可以发现,根据block的类型不同,block存放在不同的区域中。 数据段中的__NSGlobalBlock__直到程序结束才会被回收,不过我们很少使用到__NSGlobalBlock__类型的block,因为这样使用block并没有什么意义。
__NSStackBlock__类型的block存放在栈中,我们知道栈中的内存由系统自动分配和释放,作用域执行完毕之后就会被立即释放,而在相同的作用域中定义block并且调用block似乎也多此一举。
__NSMallocBlock__是在平时编码过程中最常使用到的。存放在堆中需要我们自己进行内存管理。
Block类型的定义
block是如何定义其类型,依据什么来为block定义不同的类型并分配在不同的空间呢?首先看下面一张图
// MRC环境!!!
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Global:没有访问auto变量:__NSGlobalBlock__
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:访问了auto变量: __NSStackBlock__
int a = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", a);
};
NSLog(@"1.%@, 2.%@,3.%@", [block1 class], [block2 class], [[block2 copy] class]);
}
return 0;
}
///打印如下:
1.__NSGlobalBlock__, 2.__NSMallocBlock__,3.__NSMallocBlock__
通过打印的内容可以发现正如上图中所示。 没有访问auto变量的block是__NSGlobalBlock__类型的,存放在数据段中。 访问了auto变量的block是__NSStackBlock__类型的,存放在栈中。 __NSStackBlock__类型的block调用copy成为__NSMallocBlock__类型并被复制存放在堆中。
上面提到过__NSGlobalBlock__类型的我们很少使用到,因为如果不需要访问外界的变量,直接通过函数实现就可以了,不需要使用block。
但是__NSStackBlock__访问了auto变量,并且是存放在栈中的,上面提到过,栈中的代码在作用域结束之后内存就会被销毁,那么我们很有可能block内存销毁之后才去调用他,那样就会发生问题,通过下面代码可以证实这个问题。
void (^block)(void);
void test()
{
int a = 20;
block = ^{
NSLog(@"block---------%d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
///打印、
block----------272632776
可以发现a的值变为了不可控的一个数字。为什么会发生这种情况呢?因为上述代码中创建的block是__NSStackBlock__类型的,因此block是存储在栈中的,那么当test函数执行完毕之后,栈内存中block所占用的内存已经被系统回收,因此就有可能出现乱得数据。查看其c++代码可以更清楚的理解
为了避免这种情况发生,可以通过copy将NSStackBlock类型的block转化为NSMallocBlock类型的block,将block存储在堆中,以下是修改后的代码。
void (^block)(void);
void test()
{
int a = 20;
block = [^{
NSLog(@"block---------%d", a);
} copy];
[block release];;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
///打印:
block---------20
那么其他类型的block调用copy会改变block类型吗?
所以在平时开发过程中MRC环境下经常需要使用copy来保存block,将栈上的block拷贝到堆中,即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。而在ARC环境下回系统会自动copy,是block不会被销毁。