Block 的语法
依稀记得自己第一眼看到 Block 的时候一脸懵逼的样子,接着是第二次,第三次,每次出现的样子怎么都不一样…… 。后来才发现,其实 Block 只有两种形式:
- Block 表达式
- Block 声明
Block表达式
先来看一个完整的 Block 表达式:
^void (int event) {
printf("block statement : %d", event);
};
```
对应的格式为:
`^` `返回值类型` `参数列表 ` `表达式`
返回值 紧跟 ` ^` 后面,最多只能有一个,可以为空,不需要括号包裹。
参数列表可以有多个,需要放在圆括号中。
表达式使用 {} 包裹,末尾需要有分号。
如果返回值类型为空(void)可以省略:
`^` `参数列表` `表达式`
`^ (int event) { printf("block statement : %d", event); };`
如果没有返回值也没有参数,则两者都可省略:
`^` `表达式`
`^{ printf("block statement : %d", event); };`
这已经是 Block 的最简表达式了。
#### Block 声明
Block 的类似于 C 语言函数的定义。与 函数的不同点在于 Block 可以使用定义在它外部的变量。
一般函数的类型声明形式为:
`int (*func)(int);`
而 Block 的声明形式为:
`int (^blk)(int);`
两种形式的不同之处在于将 `*` 改成 `^`,后面的 ` blk` 代表该 Block 表达式的名称。
Block 有以下几种使用场景:
- 自动(局部)变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
我们先来看一个完整的 Block:
```objective-c
int (^blk)(int) = ^int (int count) {
return count + 1;
};
```
等号左边为 Block 的**声明**,等号右边为 Block 的**表达式**。
Block 可以和普通变量一样进行赋值:
```objective-c
int (^blk)(int) = ^int (int count) {
return count + 1;
};
int (^blk2)(int) = blk;
printf("blk2: %d", blk2(4));
```
Block 也可以作为函数的参数传递:
```objective-C
void addFunc(int (^blk)(int)) {
int result = blk(3);
printf("%d", result);
}
```
Block 还可以使用返回值使用:
```objective-c
int (^func()) (int) {
return ^int (int count) { return count + 1; };
}
```
这里有点特殊的是 Block 作为返回值,整个表达式并没有写在函数名称的前面, 而是将 `func()` 函数名称放在了 `^` 的后面。但我们仍然可以看出其左边为 Block 的返回值,右边为 Block 的参数,在函数体内构造对应类型的 Block 表达式返回。
从上面来看 Block 书写有些复杂,不便于理解,我们可以像定义结构体那样,定义某种 Block 类型。如下就定义了一种传入 `int` 返回 `int` 类型的 Block:
```objective-c
typedef int (^blk_t) (int);
上述的表达式都可以简化为:
void func(blk_t blk) {}
blk_t func(){
return ^int (int count) { return count + 1; };
}
这下看起来跟普通变量的使用没什么区别了。
截获自动变量
何谓自动变量?我们可以理解为就是普通的变量(好废话...)。
Block 的形式跟函数很相似,也被称为 「匿名变量」。不过它有一个函数没有的功能就是能够截获自动变量,即使用函数体外部的变量,比如:
int val = 1;
void (^blk)(int) = ^void (int a) {
printf("%d\n", val + a);
};
val = 2;
blk(10);
```
这段代码的执行结果为 `11`,而不是 `12`。是因为 Block 会截获上下文变量的瞬时值,而之后的改变对其截获的值没有影响。
但是当我们在 Block 体内试图修改变量 val 的时候编译器会报错:
Variable is not assignable (missing __block type specifier)
提示我们使用 `__block` 修饰符。
如果这样定义变量 `__block int val = 1;` val 就可以在 Block 内部修改。
那如果 Block 截获 Objective-C 对象会怎么样?
```objective-c
NSMutableArray *array = [NSMutableArray new];
void (^blk)(int) = ^void (int a) {
[array addObject:[NSObject new]];
};
```
我们向截获的 array 对象加入元素,这样写没有问题,而向截获的 array 赋值的时候则会产生编译错误。该源代码截获的是 NSMutableArray 对象,如果用 C 语言来描述,即是截获 NSMutableArray 类对象的结构体指针。在 Block 内使用该指针没有问题,而赋值修改指针就会产生编译错误。
Block 使用 C 语言数组是需要特别注意的。源代码如下:
```objective-c
const char text[] = "hello word";
void (^block)(void) = ^{
printf("%c\n", text[1]);
};
Block 体内只是使用 C 语言的字符串字面量数组,而并没有修改自动变量,因此看似没有问题,但实际上会产生编译错误。
这是因为在 Block 中,截获自动变量的方法没有实现对 C 语言数组的截获(连续内存地址空间),而只能捕获普通栈上变量或者指针。
修改为如下形式就不会有问题:
const char *text = "hello word";
void (^block)(void) = ^{
printf("%c\n", text[1]);
};
本文为第三次阅读《Objective-C高级编程iOS与OSX多线程和内存管理》片段笔记,每次看都有所收获,但唯有记笔记效果是最佳的。