Block的实现
Block的实现
Block的实质
main.m
1
2
3
4
5
6
7
#include <stdio.h>
int main(int argc, const char * argv[]) {
void (^blk) (void) = ^ () { printf("Block\n"); };
blk();
return 0;
}
在命令行输入: clang -rewrite-objc main.m,可以通过clang转换成C++源码。
main.cpp(部分代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = {
0, sizeof(struct __main_block_impl_0)
};
int main(int argc, const char * argv[]) {
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
事实上,Block的部分:
1
printf("Block\n");
被转换成了C语言函数,参数__cself为指向Block的变量(__main_block_impl_0结构体的指针)。
1
2
3
4
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
现在,我们来关注__main_block_impl_0这个结构体。抛开构造函数,该结构体是这样的:
1
2
3
4
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
有两个成员变量,一个是__block_impl类型的impl,一个是__main_block_desc_0类型的指针Desc,他们的声明在上面源码也有给出。
继续关注他的构造函数:
1
2
3
4
5
6
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
这段构造函数,在main函数中被调用了:
1
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉转换部分:
1
2
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
blk结构体指针是通过__main_block_impl_0构造出来的,而__main_block_impl_0相当于Objective-C类对象。 __main_block_impl_0中的impl.isa是通过_NSConcreteStackBlock初始化的。在将Block作为Objective-C的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。
因此,Block的本质为Objective-C对象。
最后看看使用block的部分:
1
blk();
源代码:
1
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
转换后:
1
(*blk->impl.FuncPrt)(blk);
这是使用函数指针调用函数,由Block语法转换的 __main_block_func_0函数的指针被赋值在了impl.FuncPtr中。另外,也说明了 __main_block_func_0函数的参数 __cself指向Block(即 blk)值,在函数指针调用函数中,blk正是作为函数参数传递的。
截获自动变量
在Block中使用的自动变量,会被作为成员变量追加到__main_block_impl_0结构体中,没有使用的则不会被追加。
在源码中添加一个自动变量:
1
2
3
4
5
6
7
8
int main(int argc, const char * argv[]) {
int dmy = 256;
void (^blk) (void) = ^ () {
printf("%d\n", dmy);
};
blk();
return 0;
}
clang转换后:
1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int dmy;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _dmy, int flags=0) : dmy(_dmy) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
1
2
3
4
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int dmy = __cself->dmy; // bound by copy
printf("%d\n", dmy);
}
1
2
3
4
5
6
int main(int argc, const char * argv[]) {
int dmy = 256;
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, dmy));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
总的来说,“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到了Block的结构体实例(即Block自身)中。
__block说明符
解决Block不能保存值这一问题,有两种方法。
-
这3种变量
- 静态变量
- 静态全局变量
- 全局变量
-
使用__block说明符
通过clang转换后的源码分析,可以发现,使用__block说明符转换后的变量,变成一个结构体实例。原本的变量,成为了该结构体变量的一个变量。
Block存储域
Block和__block变量的实质
| 名称 | 实质 |
|---|---|
| Block | 栈上Block的结构体实例 |
| __block变量 | 栈上__block变量的结构体实例 |
Block类
| 类 | 设置对象的存储域 |
|---|---|
| _NSConcreteStackBlock | 栈 |
| _NSConcreteGlobalBlock | 程序的数据区域(.data区) |
| _NSConcreteMallocBlock | 堆 |
通过clang转换的源代码通常是_NSConcreteStackBlock对象,但是这两种情况下,Block为_NSConcreteGlobalBlock对象:
- 记叙全局变量的地方有Block语法时
- Block语法的的表达式中不使用截获的自动变量时
大多数情况下,编译器会恰当的进行判断,自动生成将Block从栈上复制到堆上。
以下情况需要程序员自己通过copy方法将Block从栈区复制到堆区:
- 向方法或函数的参数中传递Block时
不需要手动复制的情况:
-
Cocoa框架的方法且方法名中含有usingBlock等时
-
GCD的API
Block的副本:
| Block的类 | Block原区域 | 复制效果 |
|---|---|---|
| _NSConcreteStackBlock | 栈 | 从栈复制到堆 |
| _NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
| _NSConcreteMallocBlock | 堆 | 引用计数增加 |
__block变量存储域
Block从栈复制到堆时对__block变量产生的影响:
| __block变量的配置存储域 | Block从栈复制到堆的影响 |
|---|---|
| 栈 | 从栈复制到堆并被Block持有 |
| 堆 | 被Block持有 |
在多个Block中使用 __block 变量时,因为最先会将所有的Block配置在栈上,所以 __block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block持有。当剩下的Blcok从zhan复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。
截获对象
什么时候栈上的Block会复制到堆呢?
-
Block调用copy方法时
-
Block作为函数返回值返回时
-
将Block赋值给赋有__strong修饰符id类型的类或Block类型成员变量时
-
在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
Block循环引用
一个循环引用是这样子的:对象持有Block,Block又持有对象。这样,对象的dealloc方法就无法执行,造成了循环引用。
通常,我们会使用__weak修饰符声明一个弱引用的变量,来避免循环引用。在确定变量一定会被释放的情况下,也可以使用__unsafe_unretained修饰符。
来看一下一些这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
typedef void(^MyBlock)(void);
@interface MyObject : NSObject
@end
@implementation MyObject
{
MyBlock blk;
}
- (instancetype)init
{
self = [super init];
if (self) {
__block id blockSelf = self;
blk = ^ () {
NSLog(@"%@", blockSelf);
blockSelf = nil;
};
}
return self;
}
- (void)execBlock {
blk();
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
MyObject *obj = [[MyObject alloc] init];
[obj execBlock];
return 0;
}
obj实例持有blk,blk持有blockSelf,blockSelf持有obj。粗略一看,还是造成了循环引用。实际上,执行了[obj execBlock]后,blockSelf被赋值为nil,解除了循环引用。
所以说,使用__block修饰符也是可以解决循环引用的,但是为了避免循环引用,必须执行Block。