Block的存储域
1. StackBlock
#include <stdio.h>
static int a = 1;
int main() {
static int b = 2;
void (^blk)() = ^ {
a *= 2;
b *= 2;
printf("a:%d\n",a);
printf("b:%d\n",b);
};
blk();
}
2. Global Block
#include <stdio.h>
static int a = 1;
void (^globalBlock)(void) = ^{printf("a:%d\n",a);};
int main() {
globalBlock();
}
注意:GlobalBlock是全局Block,不需要截取自动变量,整个程序只需要一个实例,所以存放在全局区中即可。
或者说:如果一个StackBlock不需要截取自动变量,那么可以看做是GlobalBlock,尽管我们通过gcc看到的是NSConcreteStackBlock
。
3.Malloc Block
Block创建时都是在栈区的,那么堆区的Block从何而来呢?
堆区Block是栈区Block通过copy复制到堆区的,形成了Malloc Block。
问题1:为何要形成堆Block?
因为栈Block以及栈Block使用的__block变量,如果它们所在的变量作用域结束,Block以及__block变量都会被废弃,这会导致一些意想不到的问题;所以我们引入堆Block解决这个问题
问题2:如何形成堆Block?
简单来说,就是通过copy
,将Block以及__block变量复制到堆区,且让Block对象强引用__block变量,以保持__block变量不会被释放;当Block被释放时,没有对象引用的__block变量,也会被释放。
举个例子
typedef void(^BLK) (void);
//1. 创建一个方法,返回一个数组,数组里存放几个Block
- (NSArray *)getBlocks{
int val = 10;
return [[NSArray alloc]initWithObjects:^{NSLog(@"Block1.val:%d",val);},^{NSLog(@"Block2.val:%d",val);}, nil];
}
//2. 调用方法
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *arr = [self getBlocks];
BLK blk1 = [arr objectAtIndex:0];
blk1();
}
报错:EXC_BAD_ACCESS
原因:数组里的Block是栈Block,超出作用域,被释放
解决方法:
将返回的两个Block拷贝到堆区即可
- (NSArray *)getBlocks{
int val = 10;
return [[NSArray alloc]initWithObjects:[^{NSLog(@"Block1.val:%d",val);} copy],[^{NSLog(@"Block2.val:%d",val);} copy], nil];
}
以上两个问题,可参考《Objective-C高级编程》108页开始的2.3.4章节。这里主要说说以下两点:
- __block变量的存储区域
- Block为何能够使变量超出变量作用域仍可使用?
2.1 __block变量的存储区域
- Block创建时是存放在栈区的,__block修饰的变量也是在栈区的
- Block经过copy,被复制到堆区之后,Block需要使用的__block修饰的变量也会被复制到堆区
typedef void(^Block2)(void);
__block int a = 2;
Block2 block2 = [^{
a++;
NSLog(@"a:%d",a);
} copy];
a++;
block2();
结果:a:4
上面的代码中,有两个
a++
的操作
- Block中的a++,按照我们上面的说法,a和block2已经从栈区复制到堆区了;
- 第二个a++,操作的还是栈区的a++,跟复制到堆区的a++无关
那么结果不应该是3么?为什么还是4?
这就要扯到Block以及__block的底层代码了:
__block变量其实是一个C语言结构体,其中包含了int类型的值a,还有一个__forwarding
指针,这个__forwarding
指针,平时是指向结构体本身的;但是当__block变量从栈区复制到堆区之后,这个__forwarding
指针就不再指向栈区的结构体了,而是指向堆区的__block变量的结构体,这样一来,无论我们修改栈区a的值还是堆区a的值,其实都是在修改堆区a的值(如下图)
2.2 Block为何能够使变量超出变量作用域仍可使用?
typedef void(^BLK2) (id);
BLK2 blk2;
//----------变量作用域begin----------
{
NSMutableArray *array = [NSMutableArray array];
blk2 = ^(id obj){
[array addObject:obj];
NSLog(@"count:%ld",weakArray.count);
};
}
//----------变量作用域end----------
blk2([NSObject new]);
blk2([NSObject new]);
blk2([NSObject new]);
结果
count:1
count:2
count:3
很明显,上面代码中的array的作用域仅限于大括弧内,但是我们在变量作用域结束后调用blk2,仍然可以为array添加元素;这是为什么呢?
这是因为blk2结构体中,存在一个id __strong array;
,该array强引可变数组array,因此变量作用域结束后,可变数组不会被释放,我们仍然可以像可变数组array中写入数据;
那么如何使可变数组array不被block结构体中的array强引呢?
很简单,使block中的strong array强引一个weakArray即可
typedef void(^BLK2) (id);
BLK2 blk2;
{
NSMutableArray *array = [NSMutableArray array];
__weak typeof(array) weakArray = array;
blk2 = ^(id obj){
[weakArray addObject:obj];
NSLog(@"count:%ld",weakArray.count);
};
}
blk2([NSObject new]);
blk2([NSObject new]);
blk2([NSObject new]);
结果
count:0
count:0
count:0
弱引.png