【发布时间】:2013-07-23 15:00:59
【问题描述】:
我知道 Objective-C 块可以捕获和设置其封闭范围之外的变量值。它是怎么做到的?
【问题讨论】:
-
我认为它基本上将其视为外部变量在定义的范围结束时不会超出范围,使用了很多块,但我不会认为自己是专家
标签: objective-c objective-c-blocks
我知道 Objective-C 块可以捕获和设置其封闭范围之外的变量值。它是怎么做到的?
【问题讨论】:
标签: objective-c objective-c-blocks
它实际上相当简单,并在 Clang 的块实现规范中进行了描述,在 "Imported Variables" 部分。
当编译器遇到像这样的块时:
^{ if( numBalloons > numClowns) abort(); }
它创建了一个文字结构,其中包括——除其他外——两个在这里很重要的元素。块中有一个指向可执行代码的函数指针,块内引用的每个变量都有一个const 字段。像这样的:
struct __block_literal_1 {
/* other fields */
void (*invoke)(struct __block_literal_1 *);
/* ... */
const int numBalloons;
const int numClowns;
};
请注意,invoke 函数将采用指向此处定义的那种结构的指针;也就是说,Block 在执行其代码时将自己传入。因此,代码可以访问结构的成员。
在声明之后,编译器会创建一个 Block 的定义,它只是使用引用的变量来初始化 struct 中的正确字段:
struct __block_literal_1 __block_literal_1 = {
/* Other fields */
__block_invoke_2, /* This function was also created by the compiler. */
/* ... */
numBalloons, /* These two are the exact same variables as */
numClowns /* those referred to in the Block literal that you wrote. *
};
然后,在 invoke 函数内部,对捕获的变量的引用就像结构中的任何其他成员 the_block->numBalloons 一样。
对象类型变量的情况稍微复杂一些,但原理相同。
【讨论】:
在块对象的代码体中,变量可以用五种不同的方式处理。
您可以引用三种标准类型的变量,就像从函数中一样:
- 全局变量,包括静态局部变量
- 全局函数(在技术上不是变量)
- 封闭范围内的局部变量和参数
块还支持另外两种类型的变量:
在函数级别是
__block变量。它们在块(和封闭范围)内是可变的,并且如果任何引用块被复制到堆中,它们就会被保留。
const导入。最后,在方法实现中,块可以引用 Objective-C 实例变量——请参阅对象和块变量。
以下规则适用于块内使用的变量:
全局变量是可访问的,包括存在于封闭词法范围内的静态变量。
传递给块的参数是可访问的(就像函数的参数一样)。
封闭词法范围的本地堆栈(非静态)变量被捕获为
const变量。它们的值取自程序中块表达式的位置。在嵌套块中,值是从最近的封闭范围捕获的。
使用
__block存储修饰符声明的封闭词法范围的局部变量由引用提供,因此是可变的。任何更改都会反映在封闭词法范围中,包括在同一封闭词法范围内定义的任何其他块。这些在 __block 存储类型中有更详细的讨论。
在块的词法范围内声明的局部变量,其行为与函数中的局部变量完全相同。
块的每次调用都会提供该变量的新副本。这些变量又可以用作
const或块中包含的块中的引用变量。
【讨论】:
self 的强引用,并且实例变量将反映块执行时存在的任何值。这可能会令人困惑。
基本上,块“对象”包含块对象内部的变量(如块对象的“实例变量”),用于每个捕获的局部变量。 (Josh Caswell 的回答提供了有关如何实现的更多详细信息。)创建块时,将当时捕获的每个局部变量的值复制到块内的相应变量中。每当在块内使用变量时,它都会在块内使用此变量。
【讨论】: