OC高级编程-IOS与OS X多线程和内存管理--2.Blocks

1 Blocks概要

1.1 什么是Blocks

Blocks是C语言的扩充功能,带有局部变量的匿名函数,相当于C++中的Lambda表达式。
Blocks 提供了类似由C++和Objective-C 类生成实例或对象来保持变量值的方法,其代码量与编写C 语言函数差不多。

C++和Objective-C要想保持变量的值,必须要使用类、静态变量、全局变量,但是代码量太多。而Blocks则相当于函数。

程序语言 Block的名称
C+ Blocks Block
Smalltalk Block
Ruby Block
LISP Lambda
Python Lambda
C++ 11 Lambda
Javascript Anonymous function
表1 其他程序中Block的名称

2 Blocks模式

2.1 Block语法

1
2
^ 返回值类型 参数列表 表达式
^ void (int a) {···}

“返回值类型”可省略。
若没有参数,“参数列表”可省略。

2.2 Block类型变量

类型为Block的变量,可相互赋值

作用:自动变量、函数参数、静态变量、静态全局变量、全局变量

Block类型的变量的声明与使用
1
2
3
4
5
int (^blk)(int) = ^(int counts){return count+1;};
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1; //Block类型可相互赋值
int result = blk(10); //Block类型变量的使用类似于函数指针

由于Block类型变量在函数参数和返回值中使用时,记述方式极为复杂,可用typedef来解决:
typedf int (^blk_t)(int);
则在使用时:
void func(int (^blk)(int)){}==>>void func(blk_t blk){}
int (^func() (int)){}==>>blk_t func(){}

有Block类型变量,也有Block指针类型变量:

1
2
blk_t *blkptr = &blk; //Block指针定义
int a = (*blkptr)(10); //Block指针调用

2.3 截获自动变量值

Block语法保存的是局部变量的瞬时值,之后即使该变量改变,Block中的变量值也不变。

1
2
3
4
5
6
int val = 10;
int result = 0;
void (^blk)(void) = ^{result = val;};
blk(); //result为10,blk截获到val的值
val = 2
blk(); //result仍然为10

2.4 __block说明符

在Block块中无法改变被捕获的变量的值。
若想在Block中改变该变量的值,需要给其附加上__block说明符。

1
2
3
__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk(); //val值变为1

2.5 截获的自动变量

给截获的自动变量赋值会报错,但是调用截获的自动变量的自身的更改其对象的方法则不会报错。
截获C语言的数组时,也会报错,可用指针来解决。

3 Blocks的实现

3.1 Block的实质

Block即为Objective-C的对象

clang (LLVM编译器)通过“-rewrite-objc ” 选项就能将含有Block 语法的源代码变换为C++的源代码(是使用了struct 结构的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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//Block语法
int main(){
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}

//^{printf("Block\n") 转换后的代码。
//__cself为指向Block的变量,相当于C++的this、OC的self。
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
printf("Block\n");
}
//__main_block_impl_0结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
//__block_impl结构体
struct __block_impl {
void *isa;//指向对象所属的类,根据这个类模板能够创建出实例变量、实例方法等
int Flags;//某些标注
int Reserved;//版本升级所需区域
void *FuncPtr;//函数指针
};
//__main_block_desc_0结构体
struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
};
//__main_block_impl_0的构造函数
__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;
}
//_NSConcreteStackBlock相当于class_t结构体实例
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
};
//__main_block_impl_0构造函数的调用
//第一个参数:Block语法转换的C语言函数指针
//第二个参数:作为静态全局变量初始化的_main_block_desc_0 结构体实例指针
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;
//__main_block_desc_0_DATA
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
}

//Block结构体可记为
struct __main_block_impl_0 {
void *isa;
int Flag;
int Reserve;
void *FuncPtr;
struct __main_block_desc_0* Desc;
}

//blk() 转换后的代码。使用函数指针调用函数
(*blk->impl.FuncPtr)(blk);//blk作为FuncPtr的参数__cself的实参

3.2 截获自动变量值

Block语法中使用的自动变量会追加到__main_block_impl_0结构体中

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
int main(){
int dmy = 256;
int val = 10;
const chat *fmt = "val = %d\n";
void (^blk)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
fmt,
val);
return 0;
}

struct __main_block_impl_0 {
struct __block_impl impl;
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){//此处自动变量被截获
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static struct __main_block_func_0(struct __main_block_impl_0 *__cself){
const chat *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt, val);
}

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的匿名函数^{printf(fmt, val)}会被转换为:

1
2
3
4
5
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
vonst chat *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt, val);
}

3.3 __block说明符

Block中不能改变截获的自动变量,可用“__block存储域说明符”来解决。

C语言中的存储类说明符有:typedef, extern, static, auto, register
__block将该变量变成栈上生成的__Block_byref_val_0结构体类型的变量,相当于该结构体持有原变量。

访问__block变量:__Block_byref_val_0结构体有个成员变量__forwarding,__forwarding变量指向实例自身,通过它来访问__block变量的值。

![图2.1 访问__block变量 ](https://github.com/MrConfused/img/blob/master/IOS/OC/OC%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B-IOS%E4%B8%8EOS_X%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E5%9B%BE2.1%20%E8%AE%BF%E9%97%AE__block%E5%8F%98%E9%87%8F.jpg?raw=true)
图2.1 访问__block变量

3.4 Block存储域

3.4.1 Block与__block变量

表1 Block与__block变量的实质
名称 实质
Block 栈上Block的结构体实例
__block变量 栈上__block变量的结构体实例

3.4.2 Block的类及其存储域

表2 Block的类
设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的数据区域(.data 区)
_NSConcreteMallocBlock
![图2.2 设置Block的存储域 ](https://github.com/MrConfused/img/blob/master/IOS/OC/OC%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B-IOS%E4%B8%8EOS_X%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E5%9B%BE2.2%20%E8%AE%BE%E7%BD%AEBlock%E7%9A%84%E5%AD%98%E5%82%A8%E5%9F%9F.jpg?raw=true)
图2.2 设置Block的存储域

3.4.2.1 _NSConcreteGlobalBlock

全局变量处使用Block时,或者Block在函数内但不截获自动变量时,生成的Block是_NSConcreteGlobalBlock对象,Block结构体的成员变量isa 的初始化:impl.isa = &_NSConcreteGlobalBlock;

3.4.2.2 _NSConcreteMallocBlock

![图2.3 从栈复制到堆上的Block与__block变量.png ](https://github.com/MrConfused/img/blob/master/IOS/OC/OC%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B-IOS%E4%B8%8EOS_X%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E5%9B%BE2.3%20%E4%BB%8E%E6%A0%88%E5%A4%8D%E5%88%B6%E5%88%B0%E5%A0%86%E4%B8%8A%E7%9A%84Block%E4%B8%8E__block%E5%8F%98%E9%87%8F.png?raw=true)
图2.3 从栈复制到堆上的Block与__block变量

设置在栈上的Block和__block变量在作用域结束时会被废弃,通过将其复制到堆上可解决此问题。
1.复制到堆上的Block 将_NSConcreteMallocBlock类对象写入Block 结构体实例的成员变量isa。
2.__block 变量通过结构体成员变量__forwarding ,只要__forwarding 指向堆上的结构体实例,就可以实现无论__block 变量配置在栈上还是堆上时都能够正确地访问__block 变量。

3.4.2.3 何时栈上的Block 会复制到堆?

  1. 调用Block 的copy 实例方法时。
  2. Block 作为函数返回值返回时。
  3. 将Block 赋值给附有__strong 修饰符id 类型的类或Block 类型成员变量时。
  4. 在方法名中含有usingBlock 的Cocoa 框架方法或Grand Central Dispatch 的API 中传递Block 时。

大多数情况下编译器会适当地进行判断(如上述2、3、4),自动调用_Block_copy函数将Block从栈复制到堆上。

3.4.2.4 调用copy方法手动将Block复制到堆

向方法或函数的参数中传递Block 时,编译器不能判断是否自动生成复制代码,需要手动生成:
使用第一章中的copy方法:blk = [blk copy];或者[^{···} copy]

但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制了。如以下方法不用手动复制。

  1. Cocoa 框架的方法且方法名中含有usingBlock 等时。
  2. Grand Central Dispatch 的API。

对于己在堆上或者数据区的Block ,调用copy 方法又会如何呢?

表3 不同存储位置的Block调用Copy后的处理
Block的类 副本原存储位置 复制效果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么都不做
_NSConcreteMallocBlock 引用计数增加

不管Block 配置在何处,用copy 方法复制都不会引起任何问题。在不确定时调用copy方法即可

3.5 __block变量存储域

3.5.1 一个Block 中使用__block 变量

若在一个Block 中使用__block 变量,则当该Block 从栈复制到堆时,使用的所有__block变量也全部被从栈复制到堆。 此时, Block 持有__block 变量
即使在该Block 己复制到堆的情形下,复制Block 也对所使用的__block 变量没有任何影响

表4 __block变量复制到堆上时的影响
__block变量的存储域 从栈复制到堆上的影响
从栈复制到堆,并被Block持有
被Block持有
![图2.4 将Block从栈复制到堆时对__block变量的影响.png ](https://github.com/MrConfused/img/blob/master/IOS/OC/OC%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B-IOS%E4%B8%8EOS_X%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E5%9B%BE2.4%20%E5%B0%86Block%E4%BB%8E%E6%A0%88%E5%A4%8D%E5%88%B6%E5%88%B0%E5%A0%86%E6%97%B6%E5%AF%B9__block%E5%8F%98%E9%87%8F%E7%9A%84%E5%BD%B1%E5%93%8D.png?raw=true)
图2.4 将Block从栈复制到堆时对__block变量的影响

3.5.2 多个Block 中使用__block 变量

在多个Block 中使用__block 变量时,因为最先会将所有的Block 配置在栈上, 所以__block变量也会配置在栈上。
在任何一个Block 从栈复制到堆时, __block 变量也会一并从栈复制到堆并被该Block 所持有。
当剩下的Block 从栈复制到堆时,被复制的Block 持有__block 变量,并增加__block 变量的引用计数。
如果配置在堆上的Block 被废弃,那么它所使用的__block 变量也就被释放。

3.5.3 为何不管__block 变量配置在栈上还是在堆上,都能够正确地访问该变量?

在__block 变量从栈复制到堆上时, 会将成员变量__forwarding 的值替换为堆上的__block 变量结构体实例的地址

![图2.5 将Block从栈复制到堆时对__forwarding的影响.png ](https://github.com/MrConfused/img/blob/master/IOS/OC/OC%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B-IOS%E4%B8%8EOS_X%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E5%9B%BE2.5%20%E5%B0%86Block%E4%BB%8E%E6%A0%88%E5%A4%8D%E5%88%B6%E5%88%B0%E5%A0%86%E6%97%B6%E5%AF%B9__forwarding%E7%9A%84%E5%BD%B1%E5%93%8D.png?raw=true)
图2.5 将Block从栈复制到堆时对__forwarding的影响

3.6 截获对象

通过使用附有__strong 修饰符的自动变量, Block 中截获的对象就能够超出其变量作用域而存在。
通过BLOCK_FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF 参数,区分copy 函数和dispose 函数的对象类型是对象还是__block 变量

3.7 __block变量与对象

3.8 Block循环引用

若一个持有Block的对象,当他从栈复制到堆上后,Block将持有该对象,导致循环引用。

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
//持有Block的对象
typedef void (^blk_t)(void);
@interface MyObj : NSObject{
blk_t blk;
}
@end

//其初始化函数
@implementation MyObj
//blk持有强引用Block,会导致循环引用
- (id) init{
self = [super init];
blk = ^{NSLog(@"self = %@", self);};
return self;
}
//解决方法一:通过弱引用
- (id) init{
self = [super init];
id __weak tmp = self; //将self赋值给弱引用
blk = ^{NSLog(@"self = %@", tmp);};
return self;
}
//解决方法二:通过__block变量
- (id) init{
self = [super init];
__block id tmp = self; //将self赋值给__block变量,tmp持有self
blk = ^{
NSLog(@"self = %@", tmp);//Block持有tmp
tmp = nil;
};//self持有Block
return self;
}
- (void) execBlock{
blk();//执行赋值给blk的Block,nil赋值给tmp,tmp不在持有self,解决循环引用
}
int main(){
id o = [[MyObj alloc] init];
[o execBlock];
return 0;
}
@end

编译器会检测出循环引用。
使用__block变量的优点

  1. 通过__block 变量可控制对象的持有期间。
  2. 在不能使用__weak 修饰符的环境中不使用__unsafe_unretained 修饰符即可(不必担心悬垂指针)。
  3. 在执行Block 时可动态地决定是否将nil 或其他对象赋值在__block 变量中。

使用__block变量的缺点:必须执行Block。

3.9 copy/release

ARC 无效时,一般需要手动将Block 从栈复制到堆。用copy实例方法来复制.用release实例方法来释放。

只要Block 有一次复制并配置在上,就可通过retain实例方法持有该Block。

对于配置在上的Block,使用copy实例方法来持有Block。

ARC 无效时,使用__block 说明符来避免Block 中的循环引用。

当Block 从栈复制到堆时,若Block 使用的变量为附有__block 说明符的id 类型或对象类型的自动变量,不会被retain ;若不是,则被retain。

且此时不需要执行Block。

0%