看 Block 不再懵逼

2016-10-01  本文已影响39人  SmallflyBlog

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多线程和内存管理》片段笔记,每次看都有所收获,但唯有记笔记效果是最佳的。

上一篇 下一篇

猜你喜欢

热点阅读