美文网首页
看 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 不再懵逼

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