美文网首页
看 Block 不再懵逼

看 Block 不再懵逼

作者: SmallflyBlog | 来源:发表于2016-10-01 10:19 被阅读39次

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

相关文章

  • 看 Block 不再懵逼

    Block 的语法 依稀记得自己第一眼看到 Block 的时候一脸懵逼的样子,接着是第二次,第三次,每次出现的样子...

  • 我就是我

    “懵逼树上懵逼果 懵逼树下懵逼的我 我望着懵逼树上的懵逼果 一个懵逼果砸到懵逼的我 我吃完懵逼果 一脸懵逼” 性情...

  • (⊙o⊙)哇

    懵逼果 懵比树上懵逼果,懵逼树下你和我,懵逼树下排排坐,一人一个懵逼果,懵逼还有你和我,原来懵逼不止我,懵逼树下懵...

  • 0208-"饿懵圈了"

    饿 饿,饿,饿 曲项用刀割, 拔毛烧开水, 点火盖上锅。 懵逼国有懵逼路,懵逼路旁懵逼树,懵逼树上懵逼果,懵逼树下...

  • 凡人轶事37:懵逼树之歌

    懵逼树上懵逼果,懵逼树下你和我。 摘果砍树挖新坟,坟里睡着懵逼人。 今生睡在懵逼坟,来世还做懵逼人。 懵逼世道懵逼...

  • 斗觅生日快乐啊啊啊啊啊啊

    嗯? 懵逼的我 懵逼的上了简书, 懵逼的看着更新 懵逼的翻到了媚音的文 懵逼的发现斗觅生日 懵逼的大吃一惊 懵逼的...

  • monkey

    一脸懵逼,直接看的wp: exp

  • 懵逼

    有点懵逼,可能现在完全、根本不适合

  • 懵逼

    我们聊的不久,你就跟我说过三次我怕耽误你,这句好像全然为我考虑的话,却次次让我伤心不已。我们了解不多,我问你:你喜...

  • 懵逼

    快考试了,最近比较焦虑…… 突然发现竟然有若干天没有在简书上记下一笔了,吃惊! 原来是因为我周一请了一天的假(周末...

网友评论

      本文标题:看 Block 不再懵逼

      本文链接:https://www.haomeiwen.com/subject/ytpryttx.html