关于block的存储域
一、 block变量存储域
1. ARC和MRC不同的存储情况
通过对block本质的探究,发现block内部也是有一个isa指针指向它所属的类,所以block其实也是一个对象。
// ARC 环境下
void (^blk)() = ^{
};
NSLog(@"%@",[blk class]);
其打印结果为 " _ _NSGlobalBlock _ _ ",可想而知block是属于一个叫NSGlobalBlock类的对象。其实 block的类有三种。分别为 NSGlobalBlock ,NSStackBlock ,NSMallocBlock。从它们的名字能猜测到,这三种类型的block对应着block不同的存储区域。相继为 global(全局数据区域) stack (栈区) malloc(堆区)。
分别在ARC和MRC的情况下看看哪些情况下对应的block存储区域,首先是ARC的情况下
NSLog(@"未赋值 未引用外部变量 %@", [^void() {
} class]);
void (^blk)() = ^{
};
NSLog(@"赋值 未引用外部变量 %@",[blk class]);
int a = 1;
void (^blk1)() = ^{
a;
};
NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
NSLog(@"未赋值 引用外部变量 %@", [^void() {
a;
} class]);
最后的输出结果为:
2017-01-08 21:49:15.389 block存储域[10956:1104789] 未赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 引用外部变量 __NSMallocBlock__
2017-01-08 21:49:15.390 block存储域[10956:1104789] 未赋值 引用外部变量 __NSStackBlock__
可以对ARC的情况下做一个总结:
- NSGlobalBlock === 在block未引用外部变量的时候
- NSMallocBlock === 在block引用了外部变量的时候,且将block赋值给了block类型的变量
- NSStackBlock === 在block引用了外部变量的时候, 没有将block赋值给block类型的变量时。
接下来是对在MRC的情况下,block的不同存储区域
NSLog(@"未赋值 未引用外部变量 %@", [^void() {
} class]);
void (^blk)() = ^{
};
NSLog(@"赋值 未引用外部变量 %@",[blk class]);
int a = 1;
void (^blk1)() = ^{
a;
};
NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
NSLog(@"未赋值 引用外部变量 %@", [^void() {
a;
} class]);
输出结果为:
2017-01-08 21:56:21.789 block存储域[10994:1108230] 未赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 引用外部变量 __NSStackBlock__
2017-01-08 21:56:21.791 block存储域[10994:1108230] 未赋值 引用外部变量 __NSStackBlock__
我们可以看到无论是否引用外部变量和是否赋值给了block类型的变量,都没有出现mallocBlock,那么MRC的情况下如何才会生成mallocBlock呢。这时我们需要调用对象的copy方法。
void (^blk)() = ^{
};
NSLog(@"copy 未引用外部变量 %@",[[blk copy] class]);
int a = 1;
void (^blk1)() = ^{
a;
};
NSLog(@"copy 引用外部变量 %@",[[blk1 copy] class]);
2. ARC情况下什么时候会进行copy
block引用了外部变量就会存储在栈区,在ARC环境下大多数情况会自动copy操作后复制到堆区。但是有些情况下编译器不会自动进行copy操作:
-
block作为函数参数的时候不会自动进行copy操作,除了以下情况之外:
(1) Cocoa框架的方法切方法名中又usingBlock。
(2) GCD的API。
通过以下代码验证:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *tempArray = [self getBlockArray];
blk_t blk = tempArray[0];
blk();
}
- (id)getBlockArray {
int a = 10;
return [[NSArray alloc] initWithObjects:^{
NSLog(@"blk1 %d",a);
},^{
NSLog(@"blk2 %d",a);
}, nil];
}
因为作为参数传递的block的存储区域为栈区,而出了 getBlockArray 函数后,block的作用域结束了,当调用函数取得数组时,访问已经释放了的block,就会造成了crash。要是想正确访问block ,可以做以下修改,对作为参数传递的block进行copy操作。
- (id)getBlockArray {
int a = 10;
return [[NSArray alloc] initWithObjects:[^{
NSLog(@"blk1 %d",a);
} copy],[^{
NSLog(@"blk2 %d",a);
}copy], nil];
}
二、 __block变量存储域
我们知道对于block内部使用的外部变量,不允许在block内部修改外部变量的值。以下代码是编译不过的。
int a = 10;
void (^blk)() = ^(){
a = 20;
} ;
blk();
因为在block进行copy的同时,会将block内使用的外部变量也进行一次copy操作,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。
int a = 10;
NSLog(@"定义block前 %p",&a);
void (^blk)() = ^(){
NSLog(@"block内部 %p",&a);
} ;
blk();
NSLog(@"定义block后 %p",&a);
打印结果为:
2017-02-08 14:20:06.716 block存储域_test[23117:147534] 定义block前 0x7fff5b635a4c
2017-02-08 14:20:06.717 block存储域_test[23117:147534] block内部 0x60800005a1b0
2017-02-08 14:20:06.717 block存储域_test[23117:147534] 定义block后 0x7fff5b635a4c
16进制转换为10进制,block外部为 140734726625868 ,block内部使用的时候为 106102872449456,2者相差1532868764个字节,转换为1461 mb。因为堆地址远小于栈中的地址,又因为iOS中一个进程的栈区内存只有 1mb,所以在block内部 的变量已经在堆区内了。
__block 变量
对于block内部使用 _ _block变量时,和普通的变量一样,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。但是为什么能在block内部修改 _ _block修饰的变量。
__block int a = 10;
NSLog(@"定义block前 %p",&a);
void (^blk)() = ^(){
a++;
NSLog(@"block内部 %p",&a);
} ;
NSLog(@"定义block后 %p",&a);
blk();
打印的结果为:
2017-02-08 14:44:01.933 block存储域_test[26207:167214] 定义block前 0x7fff569eca48
2017-02-08 14:44:01.934 block存储域_test[26207:167214] 定义block后 0x60800003eb38
2017-02-08 14:44:01.934 block存储域_test[26207:167214] block内部 0x60800003eb38
与普通的外部变量不同的是,定义block后,变量的地址也指向了堆中的那个地址。而不是定义之前的栈中地址。还记得__block内部那个指向 变量自身的那个forwading指针吗,当 _ _block变量赋值到堆区时,栈区的变量的forwading指针也改为了指向堆中的那个变量的地址,所以在block外部使用变量时,它访问的是堆中的那个变量
a++ <===> a._forwarding ->a)++
三、 截获的对象存储域
通常的一个变量,如下,当它超过了作用域之后,就会被释放了。
- (void)test5 {
NSMutableArray *tempArray = [NSMutableArray array];
}
但是对于block中使用的外部变量,在某些情况下,却能超过变量的作用域存在。
(该代码 是在ARC环境下运行)
blk_t globalblk;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
globalblk();
}
- (void)test5 {
NSMutableArray *tempArray = [NSMutableArray array];
globalblk = ^{
id obj = [[NSObject alloc] init];
[tempArray addObject:obj];
NSLog(@"count == %ld a == %d", tempArray.count,a);
};
}
该代码中,tempArray变量出了 test5这个函数,就超出了作用域,但是该代码却能正确的打印出来。说明: 在ARC环境下,当给block赋值给一个block变量时,将会默认的对block进行一次copy操作。如果在MRC环境下,也就是没有对block进行手动copy的话,这段代码就会crash。
四、 __block和对象
关于__block修饰的对象,被block使用的时候,在ARC和MRC情况下,是有很大的不同的。当在ARC环境下,block内部使用 _ _block修饰的对象的时候,它和正常的外部变量一样,当block从栈区copy到堆区时,会被block所持有,因此能超出变量总用域存在。而在MRC的情况下,当 _ _block修饰的变量被block使用时,不会随着block从栈区copy到堆区而被block所持有。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
globalblk();
}
- (void)test5 {
__block NSMutableArray *tempArray = [NSMutableArray array];
globalblk = [^{
id obj = [[NSObject alloc] init];
[tempArray addObject:obj];
NSLog(@"count == %ld", [tempArray count]);
} copy];
}
以上的代码,在ARC环境下能正常运行,在MRC情况下会因为野指针的访问而crash。
五、 block的循环引用
block持有_ _strong修饰的外部变量时,block会持有该对象,而如果该对象又持有block时,就会造成循环引用的问题。
typedef void (^blk_t)();
@interface Person ()
{
blk_t blk;
}
@end
@implementation Person
- (instancetype)init {
if (self = [super init]) {
blk = [^(){
NSLog(@"%@",self);
} copy];
}
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
}
该段代码的dealloc一定不会被打印,因为在调用 Person的init函数是,成员变量blk使用了 self,使得blk对 self 持有。而blk作为 Person对象的成员变量,Person对象就对 blk持有。造成了这种互相持有的关系后,该对象就无法释放了。
循环引用问题.png解决方法(1): 通过对block内部使用的变量,改为weak修饰。
- (instancetype)init {
if (self = [super init]) {
__weak typeof(self) weakSelf = self;
blk = [^(){
NSLog(@"%@",weakSelf);
} copy];
}
return self;
}
解决方法(2): 之前说过,在MRC环境下,用_ _block修饰变量时,block不会对该变量进行持有。
//仅对MRC环境下有效
- (instancetype)init {
if (self = [super init]) {
__block tempSelf = self;
blk = [^(){
NSLog(@"%@",tempSelf);
} copy];
}
return self;
}
网友评论