iOS与OS X多线程和内存管理 - Blocks
前言
iOS与OS X多线程和内存管理 读书笔记(二) Blocks
笔记
Blocks概要
Blocks:带有自动变量(局部变量)的匿名函数
完整的Block语法与一般的C语言函数定义相比只有2点不同:
- 开头多了^符号
- 没有函数名
看看如何声明Block类型变量:
int (^blk)(int);
声明了一个参数类型为int,返回类型为int的Block变量blk。typedef int (^blk_t)(int);
那么blk_t代表一个Block变量Block会自动截获和保存变量值。但是不能更改保存的变量值,除非你使用__block。如下:
1
2
3
4
5
6
7
8int val = 0;
void (^blk)(void) = ^{val = 1}; // 更改自动保存的变量值 报错
blk();
/* 使用__block之后 */
__block int val = 0;
void (^blk)(void) = ^{val = 1}; // 不报错
blk();PS:如果是截获的OC对象,比如截获了NSMutableArray对象array,可以使用array:
[array addObject:obj];
;但是不能给array赋值:array = [[NSMutableArray alloc] init];
。若要给他赋值,还是得使用__block修饰符。PS:不会自动截获C语言数组:
char text[] = "hello";
;但是可以截获指针:char *text = "hallo";
。
Blocks的实现
Block其实就是Objective-C对象
来看看具体实现
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
41
42
43
44
45
46
47
48
49int main() {
void (^blk)(void) = ^{printf("Bolck.../n");};
blk();
return 0;
}
/* 经过编译器转化之后 */
/* 下面的结构体表示的是Block对象 */
struct __main_block_impl_0 {
void *isa; // OC对象都有一个isa指向它的类,表明这个对象是一个什么类
/* 无关紧要的一些成员 */
int Flags;
int Reserved;
struct __main_block_desc_0 *Desc; // 一个结构体指针,后面会有定义,大概和系统有关
void *FuncPtr; // 函数指针,指向这个Block的匿名函数,也就是 {printf("Block.../n");}
/* 结构体的初始化函数 */
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
// 传入的参数是匿名函数的函数指针,__main_block_desc_0结构体指针和flags
isa = &_NSConcreteStackBlock; // 初始化isa
Flags = flags;
FuncPtr = fp;
Desc = desc;
}
}
/* 下面代码的结构体 就是上面Block结构体中的那个无关理解的结构体指针成员的定义 */
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
/* 匿名函数的定义,传入的参数是Block结构体指针 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block.../n");
}
/* main函数的实现 */
int main {
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); // 初始化Block,其实也就是创建Block结构体
blk->FuncPtr(blk); // 执行Block,也就是执行Block结构体中的函数,传入结构体本身
return 0;
}上面这个编译的过程就是:
- 在main函数外面,先对Block做定义:Block是一个结构体,他有一个成员是匿名函数的函数指针;这个结构体的构造函数需要传入匿名函数的函数指针。
- 在main函数外面再定义匿名函数,匿名函数的参数是Block结构体,该参数在截获自动变量时会有用。
- 定义main函数,先新建一个Block结构体并初始化;在执行Block,也就是执行Block结构体里面的函数指针指向的函数;再return 0。
PS:结构体的初始化里面的
isa = &_NSConcreteStackBlock;
就是在将结构体当做OC对象在处理,他将OC对象的信息放在isa里面。看看如何截获自动变量
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
41
42
43
44
45
46
47
48
49
50
51
52int main() {
int dmy = 256; // 不会被截获的变量
int val = 10; // 被截获
const chat *fmt = "val = %d/n";
void (^blk)(void) = ^{printf(fmt, val);};
blk();
return 0;
}
/* 经过编译之后 */
/* Block结构体定义 */
struct __main_block_impl_0 {
/* 如前所诉 */
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 *Desc;
/* 截获的自动变量,变成了结构体成员 */
const char *fmt;
int val;
/* 初始化函数,这里会使用截获的自动变量对成员进行赋值 */
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) :fmt(_fmt), val(_val) {
/* 如前所诉,就省略了 */
//...
}
}
/* __main_block_desc_0结构体定义,同上,省略 */
/* 匿名函数定义 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt, val);
}
/* main函数实现 */
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d/n";
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &main_block_desc_0_DATA, fmt, val); // 这里进行Block的初始化
blk->FuncPtr(blk); // 执行Block
return 0;
}从上面可以看出,在初始化得时候,将自动变量传入了Block结构体的初始化函数,进行保存。所以之后对这些自动变量进行修改,Block结构体里面的这些保存在结构体成员里的变量是不受影响的。
__block的实现
__block变量也定义在main函数之外,不在只是一个简单的变量,而是变成了结构体:
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
41
42
43struct __Block_byref_val_0 {
void *isa;
__Block_byref_val_0 *__forwarding; // 指向自己的指针
int __flags;
int __size;
int val; // 原来的自动变量
}
/* Block里面含有__block变量的结构体指针 */
struct __main_block_impl_0 {
/* 同上的成员省略 */
__Block_byref_val_0 *val; // 指向val结构体的指针
/* 结构体初始化函数,使用val结构体的__forwarding成员变量初始化Block结构体的val指针 */
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags = 0) : val(_val->forwarding) {
/* 同上 省略 */
}
}
/* 匿名函数 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val;
val->forwarding->val = 1; // 在这里 val和val->forwarding其实都指的是自己
}
/* main函数里面 */
int main() {
int val = 10;
void (^blk)(void) = ^{val = 1;};
}
/* 转化之后 val变成结构体 */
int main() {
__Block_byref_val_0 val = {
0,
&val, // 指向自身的指针
0,
sizeof(__Block_byref_val_0),
10 // 初始化
};
blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val); // 初始化Block结构体时将val结构体地址传过去
}将val变量变成存放在栈上的结构体,这也就是__block变量
Block存储域
- _NSConcreteStackBlock将Block存放在栈上
- _NSConcreteGlobalBlock将Block存放在数据区域
- _NSConcreteMallocBlock将Block存放在堆上
存放在数据区域的Block相当于全局变量,不能截获自动变量,全局只有一个实例。所有不需要截获自动变量的Block都是这种类型的。
其他的Block都现在栈上生成,但是当超过这个Block的作用域,但是又要使用该Block时,就会将Block从栈上复制到堆上。例如下面的例子:
1
2
3
4typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count)(return rate *count);
}这里Block在函数内生成后,返回时就已经离开Block的作用域了。这时候就需要将他copy到堆上
1
2
3
4
5blkt func(int rate) {
blkt tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = _Block_copy(tmp); // 将栈上的Block复制到堆上
return objc_autoreleaseReturnValue(tmp); // 将tmp注册到NSAutoreleasePool,说明Block也是OC对象
}出了函数之后,栈上的Block就会被释放,这个时候只能使用在堆上的Block了。很多情况下编译器会帮助我们自动复制的,但是NSArray得
initWithObjects
方法就不会自动复制:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"haha");},
^{NSLog(@"yaya");}, nil];
}
// 这种情况下返回的NSArray,如果去获取NSArray中的Block对象,是会出错的,因为这些Block在出函数时被废弃了, 这时就需要我们手动复制了,也就是调用copy方法
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"haha");} copy],
[^{NSLog(@"yaya");} copy], nil];
}PS:当Block被复制到堆上时,它 使用 的__block变量也会从栈上被复制到堆上,这个时候该变量会被Block 持有。如果还有在栈上的其他Block使用block变量的话,block变量还会保留在栈上,但是他的 forwarding指针已经指向堆上的block变量了,所以改变block变量的话,始终改变的都是同一个结构体。
看看如何截获OC对象
1
2
3
4
5
6
7
8
9
10
11{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
}
/* 注意 可以理解成 Block里面的array结构体成员强引用NSMutableArray对象 */
struct __main_block_impl_0 {
id __strong array; // 强引用
}但实际上结构体对__strong是没有概念的,不过此时的结构体会手动加上ARC的概念上去。当Block从栈上复制到堆上时,OC对象被Block持有;当Block从堆上废弃时,OC对象被释放。所以,很重要的一点:只有Block被复制到堆上时,OC对象,才能被Block持有;也就是要调用Block的copy函数。
__block类型的OC对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14__block id obj = [[NSObject alloc] init];
/* 编译之后 */
struct __Block_byref_obj_0 {
void *isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // 用来使Block持有对象
void (*__Block_byref_id_object_dispose)(void*); // 用来使Block释放对象
__strong id obj;
}所以__block类型的OC对象也是讲OC对象封装成一个结构体,和之前的block类型的自动变量一样,只不过添加了retain和release的概念而已。
PS:如果对象是weak类型的,和上一篇里面分析weak和strong的一样。Block也只是含有一个指向对象的指针而已,如果该指针为weak,那么该置成nil,还是要置成nil。
看看使用Block的一个常见问题:循环引用
最常见的就是在Block中使用self,而这个Block还是self对象的一个变量。那么对象持有Block,Block又持有对象。循环引用。所以在Block中使用self经常需要使用weakself。