iOS Block 使用

什么是Block

Block是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。

C语言中可能使用的变量:

  • 自动变量(局部变量)
  • 函数的参数
  • 静态变量(静态局部变量)
  • 静态全局变量
  • 全局变量

“带有自动变量值的匿名函数”这一高年不仅指Blocks,它还存在于其他许多程序语言中。在计算机科学中,此概念也被称为闭包(Closure)、lambda计算等。

Block模式

完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:
(1)没有函数名。
(2)带有“^”。
第一点,因为它是匿名函数。
第二点,返回值类型前带有“^”(插入记号,caret)记号。macOs、iOS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。

^ 返回值类型 参数列表 表达式

返回值类型、参数列表都是可以省略的。

Block类型变量

在Block语法下,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。
声明Block类型变量的示例如下:

int (^block)(int);

声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为“^”。该Block类型变量与一般的C语言变量完全相同,可作为一下用途使用。

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量
    使用Block语法将Block赋值为Block类型变量:
int (^block)(int) = ^(int count) {return count + 1;};

在函数参数中使用Block类型变量可以向函数传递Block:

void func(int (^block) (int)) {

}

在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。

int (^func()) (int) {
    return ^(int count) {return count+1;};
}

由此可知,函数参数和返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef类解决该问题。

typedef int (^block_t) (int);
/** 原来的记述方式
void func (int (^block) (int))
*/
void func (block_t block)

/** 原来的记述方式
void func (int (^block) (int))
*/
block_t func()

截获自动变量的值

通过Block语法和Block类型变量的说明,我们已经理解了“带有自动变量值的匿名函数”中的“匿名函数”。而“带有自动变量值”是什么呢?“带有自动变量值”在Blocks中表现为“截获自动变量值”。截获自动变量值的实例如下:

int main() {

    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^block) (void) = ^{
        printf(fmt, val);
    };
    val = 2;
    fmt = "These values were changed. val = %d\n";
    block();
    return 0;
}

该源码中,Block语法的表达式使用的是它之前声明的自动变量fmt和val。Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。该源码就在Block语法后修改了Block中的自动变量val和fmt。

val = 10

执行结果并不是改写后的值“These values were changed. val = 2”,而是执行Block语法时的自动变量的瞬间值。该Block语法在执行时,字符串指针“val = %d\n”被复制到自动变量fmt中,int值10被赋值到自动变量val中,因此这些值被保存,从而在执行块时使用。
这就是自动变量值的截获。

__block修饰符

自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能修改该值。如果尝试改写截获的自动变量值:

int val = 0;
void (^block) (void) = ^{
    val = 1;
};
block();
printf("val = %d\n", val);

以上为在Block语法外声明的给自动变量赋值的源码。该源码会产生编码错误。

error: variable is not assignable (missing __block type specifier)
    void (^block) (void) = ^{
        val = 1;
    };

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block修饰符。该源码中,如果给自动变量声明int val附加__block修饰符,就能实现在Block内部赋值。

__block int val = 0;
void (^block) (void) = ^{
    val = 1;
};
block();
printf("val = %d\n", val);

该源码的执行结果为:

val = 1

使用附有__block修饰符的自动变量可在Block赋值,该变量成为__block变量。

截获的自动变量

如果将值赋值给Block中截获的自动变量,就会产生编译错误。

int val = 0;
void (^block) (void) = ^{
    val = 1;
};

编译该源码错误为:

error: variable is not assignable (missing __block type specifier)
    void (^block) (void) = ^{
        val = 1;
    };

那么截获OC对象,调用该对象的方法也会产生编译错误吗?

id array = [[NSMutableArray alloc] init];
void (^block) (void) = ^ {
    id obj = [[NSObject alloc] init];
    [array addObject:obj];
};

这是没有问题的,而向截获的变量array赋值则会产生编译错误。该源码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任务问题。

id array = [[NSMutableArray alloc] init];
void (^block) (void) = ^ {
    array = [[NSMutableArray alloc] init];
};
error: variable is not assignable (missing __block type specifier)
    array = [[NSMutableArray alloc] init];
    ~~~~~ ^

这种情况下,需要给截获的自动变量附加__block修饰符。

__block id array = [[NSMutableArray alloc] init];

void (^block) (void) = ^ {
    array = [[NSMutableArray alloc] init];
};

另外,在使用C语言数组时必须小心使用其指针。

const char text[] = "hello";
void (^block) (void) = ^ {
    printf("%c\n", text[2]);
};

只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有问题。但实际上会产生以下编译错误:

error: cannot refer to declaration with an array type inside block
    printf("%c\n", text[2]);
note: declared here
    const char text[] = "hello";
               ^

这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。这时,使用指针可以解决该问题。

const char *text = "hello";
void (^block) (void) = ^ {
    printf("%c\n", text[2]);
};

你可能感兴趣的:(iOS Block 使用)