块语言规范

修订

  • 2008/2/25 — 创建

  • 2008/7/28 — 修订,__block 语法

  • 2008/8/13 — 修订,块全局变量

  • 2008/8/21 — 修订,C++ 阐述

  • 2008/11/1 — 修订,__weak 支持

  • 2009/1/12 — 修订,显式返回类型

  • 2009/2/10 — 修订,__block 对象需要保留

概述

一个新的派生类型被引入到 C 中,并扩展到 Objective-C、C++ 和 Objective-C++

块类型

与函数类型类似,块类型是一个包含结果值类型和参数类型列表的配对,非常类似于函数类型。块旨在像函数一样使用,主要区别在于,除了可执行代码之外,它们还包含各种变量绑定到自动(栈)或托管(堆)内存。

抽象声明符:

int (^)(char, float)

描述对一个块的引用,当调用时,它接受两个参数,第一个是 char 类型,第二个是 float 类型,并返回一个 int 类型的返回值。引用的块是可能驻留在自动(栈)内存、全局内存或堆内存中的不透明数据。

块变量声明

使用函数指针样式的符号来声明具有块类型的变量,将 ^ 替换为 *。以下是有效的块变量声明

void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

支持可变参数 ... 参数。[variadic.c] 不接受任何参数的块必须在参数列表中指定 void [voidarg.c]。空参数列表不代表,如 K&R 提供的那样,一个未指定的参数列表。注意:gcc 和 clang 都出于方便性支持 K&R 样式。

块引用可以强制转换为任意类型的指针,反之亦然。[cast.c] 块引用不能通过指针解除引用运算符 * 解除引用,因此块的大小不能在编译时计算。[sizeof.c]

块字面量表达式

一个块字面量表达式生成对块的引用。它以使用 ^ 符号作为一元运算符的方式引入。

Block_literal_expression ::=   ^ block_decl compound_statement_body
block_decl ::=
block_decl ::= parameter_list
block_decl ::= type_expression

其中类型表达式扩展为允许 ^ 作为块引用(指针),而 * 被允许作为函数引用(指针)。

以下块字面量

^ void (void) { printf("hello world\n"); }

生成对没有参数并且没有返回值的块的引用。

返回值类型是可选的,它从 return 语句中推断出来。如果 return 语句返回一个值,那么它们都必须返回相同类型的返回值。如果没有返回值,则推断的块类型为 void;否则它就是 return 语句值的类型。

如果省略返回值类型并且参数列表为 ( void ),那么 ( void ) 参数列表也可以省略。

所以

^ ( void ) { printf("hello world\n"); }

^ { printf("hello world\n"); }

是同一个表达式的完全等效结构。

type_expression 扩展了 C 表达式解析以适应块引用声明,因为它适应函数指针声明。

给定

typedef int (*pointerToFunctionThatReturnsIntWithCharArg)(char);
pointerToFunctionThatReturnsIntWithCharArg functionPointer;
^ pointerToFunctionThatReturnsIntWithCharArg (float x) { return functionPointer; }

^ int ((*)(float x))(char) { return functionPointer; }

是等效的表达式,就像

^(float x) { return functionPointer; }

[returnfunctionptr.c]

复合语句体在其父级的词法范围内建立一个新的词法范围。在复合语句范围内使用的变量以正常的方式绑定到块,除了那些在自动(栈)存储中的变量。因此,可以像预期的那样访问函数和全局变量,以及静态局部变量。[testme]

在块的复合语句中引用的局部自动(栈)变量被作为常量副本导入并捕获。捕获(绑定)在块字面量表达式求值时执行。

如果编译器可以证明不会实际对变量的引用进行求值,那么它不需要捕获变量。程序员可以通过在块开头引用一个语句来强制捕获一个变量,如下所示

(void) foo;

当捕获变量有副作用时,这很重要,例如在 Objective-C 或 C++ 中。

在块中声明的变量的生命周期与函数相同;每个激活帧包含块局部范围内的变量的新副本。此类变量声明应该在任何地方都被允许 [testme],而不仅仅是在请求 C99 解析时,包括在 for 语句中。[testme]

块字面量表达式可以出现在块字面量表达式中(嵌套),并且所有被任何嵌套块捕获的变量都被隐式地也捕获在它们封闭块的范围内。

块字面量表达式可以用作全局或局部静态作用域的块变量的初始化值。

调用运算符

块使用函数调用语法进行调用,该语法带有与声明相对应类型的表达式参数列表,并根据声明返回结果类型。给定

int (^x)(char);
void (^z)(void);
int (^(*y))(char) = &x;

以下都是合法的块调用

x('a');
(*y)('a');
(true ? x : *y)('a')

复制和释放操作

编译器和运行时为块引用提供复制释放操作,这些操作创建并,在匹配使用时,释放为引用的块分配的存储。

复制操作 Block_copy() 采用函数形式,它接受任意块引用并返回相同类型的块引用。释放操作,Block_release(),采用函数形式,它接受任意块引用,如果动态匹配到块复制操作,则允许恢复引用的已分配内存。

The __block Storage Qualifier

除了新的块类型之外,我们还为局部变量引入了一个新的存储限定符,__block。[testme: 块字面量内的 __block 声明] __block 存储限定符与现有的局部存储限定符 auto、register 和 static 相互排斥。[testme] 用 __block 限定的变量的行为就好像它们在已分配的存储中一样,并且此存储在最后使用该变量后会自动恢复。实现可以选择一个优化,其中存储最初是自动的,并且只有在引用块的 Block_copy 时才“移动”到已分配(堆)存储中。此类变量可以像普通变量一样被修改。

__block 变量是块的情况下,必须假设 __block 变量驻留在已分配的存储中,因此假设它引用一个也位于已分配存储中的块(即它是 Block_copy 操作的结果)。尽管如此,如果实现为块提供了初始自动存储,则没有规定执行 Block_copyBlock_release。这是由于潜在的多个线程尝试更新共享变量的固有竞争条件以及围绕处置旧值和复制新值的同步的需要。此类同步超出了本语言规范的范围。

Control Flow

块的复合语句在控制流方面被视为与函数体非常相似,即 goto、break 和 continue 不会逃逸块。异常被视为正常,即当抛出时,它们会弹出栈帧,直到找到 catch 子句。

Objective-C Extensions

Objective-C 将块引用类型的定义扩展为 id 的定义。块类型的变量或表达式可以发送消息或用作参数,只要 id 可以出现的地方就可以。反之亦然。因此,块引用可以作为属性出现,并受限于保留给对象的 assign、retain 和 copy 属性逻辑。

所有块都被构建为 Objective-C 对象,无论程序中是否正在运行 Objective-C 运行时。使用自动(栈)内存的块是对象,可以向它们发送消息,尽管如果启用了垃圾回收,它们可能不会被分配到 __weak 位置。

在方法定义内的块字面量表达式中,对实例变量的引用也被导入到复合语句的词法范围内。这些变量被隐式地限定为来自 self 的引用,因此 self 被导入为一个常量副本。最终效果是实例变量可以被修改。

Block_copy 运算符保留块表达式中引用的自动存储变量中保存的所有对象(或如果在垃圾回收下运行则形成强引用)。__block 存储类型对象的变量被假定为保存普通指针,不提供保留和释放消息。

Foundation 为块定义(并提供) -copy-release 方法。

在 Objective-C 和 Objective-C++ 语言中,我们允许使用 __weak 说明符来修饰对象类型的 __block 变量。如果未启用垃圾回收,则此限定符会导致这些变量被保留,而不会发送保留消息。这样会导致如果 Block(或其副本)超出此对象的生存期,则会导致悬空指针。

在垃圾回收环境中,当被引用的对象被回收时,__weak 变量被设置为 nil,只要 __block 变量位于堆中(默认情况下或通过 Block_copy())。Apple 的初始实现实际上是在堆栈上启动 __block 变量,并且仅在 Block_copy() 操作的结果下迁移它们到堆中。

尝试将对基于堆栈的 Block 的引用分配给标记为 __weak 的任何存储,包括 __weak __block 变量,是运行时错误。

C++ 扩展

函数内的 Block 文字表达式被扩展以允许对保存在自动存储中的 C++ 对象、指针或引用进行 const 使用。

像往常一样,在块中,对捕获的变量的引用将变为 const 限定,就像它们是对 const 对象的成员的引用一样。请注意,这不会更改引用类型变量的类型。

例如,给定一个类 Foo

Foo foo;
Foo &fooRef = foo;
Foo *fooPtr = &foo;

引用这些变量的 Block 会将这些变量作为 const 变化导入

const Foo block_foo = foo;
Foo &block_fooRef = fooRef;
Foo *const block_fooPtr = fooPtr;

在评估 Block 文字表达式时,捕获的变量被复制到 Block 中。在调用堆栈上分配的 Block 上的 Block_copy() 时,它们也会被复制。在这两种情况下,它们都被复制,就好像变量是 const 限定的,如果不存在这样的构造函数,则会出错。

堆栈上的 Block 中的捕获变量在控制离开包含 Block 文字表达式的复合语句时被销毁。堆上的 Block 中的捕获变量在 Block 的引用计数降至零时被销毁。

声明为驻留在 __block 存储中的变量可能最初在堆中分配,或者可能首先出现在堆栈上,并作为 Block_copy() 操作的结果被复制到堆中。从堆栈复制时,__block 变量使用它们的正常限定(即不添加 const)进行复制。在 C++11 中,如果可能,__block 变量被复制为 x-value,否则被复制为 l-value;如果两者都失败,则会出错。任何初始基于堆栈的版本的析构函数将在变量的正常作用域结束时被调用。

this 的引用,以及对任何封闭类的非静态成员的引用,通过捕获 this 来进行评估,就像一个普通的 C 指针类型变量一样。

作为 Block 的成员变量不能被其参数的类型重载。