闭包

一个函数或 lambda 从定义它的静态作用域中捕获了变量,函数或 lambda 和捕获的变量一起被称为一个闭包,这样即使脱离了闭包定义所在的作用域,闭包也能正常运行。

函数或 lambda 的定义中对于以下几种变量的访问,称为变量捕获:

  • 函数的参数缺省值中访问了本函数之外定义的局部变量;

  • 函数或 lambda 内访问了本函数或本 lambda 之外定义的局部变量;

  • class/struct 内定义的不是成员函数的函数或 lambda 访问了实例成员变量或 this

以下情形的变量访问不是变量捕获:

  • 对定义在本函数或本 lambda 内的局部变量的访问;

  • 对本函数或本 lambda 的形参的访问;

  • 对全局变量和静态成员变量的访问;

  • 对实例成员变量在实例成员函数或属性中的访问。由于实例成员函数或属性将 this 作为参数传入,在实例成员函数或属性内通过 this 访问所有实例成员变量。

变量的捕获发生在闭包定义时,因此变量捕获有以下规则:

  • 被捕获的变量必须在闭包定义时可见,否则编译报错;

  • 被捕获的变量必须在闭包定义时已经完成初始化,否则编译报错。

示例 1:闭包 add,捕获了 let 声明的局部变量 num,之后通过返回值返回到 num 定义的作用域之外,调用 add 时仍可正常访问 num

func returnAddNum(): (Int64) -> Int64 { let num: Int64 = 10 func add(a: Int64) { return a + num } add } main() { let f = returnAddNum() println(f(10)) }

程序输出的结果为:

20

示例 2:捕获的变量必须在闭包定义时可见。

func f() { let x = 99 func f1() { println(x) } let f2 = { => println(y) // Error, cannot capture 'y' which is not defined yet } let y = 88 f1() // Print 99. f2() }

示例 3:捕获的变量必须在闭包定义前完成初始化。

func f() { let x: Int64 func f1() { println(x) // Error, x is not initialized yet. } x = 99 f1() }

如果捕获的变量是引用类型,可修改其可变实例成员变量的值。

class C { public var num: Int64 = 0 } func returnIncrementer(): () -> Unit { let c: C = C() func incrementer() { c.num++ } incrementer } main() { let f = returnIncrementer() f() // c.num increases by 1 }

为了防止捕获了 var 声明变量的闭包逃逸,这类闭包只能被调用,不能作为一等公民使用,包括不能赋值给变量,不能作为实参或返回值使用,不能直接将闭包的名字作为表达式使用。

func f() { var x = 1 let y = 2 func g() { println(x) // OK, captured a mutable variable. } let b = g // Error, g cannot be assigned to a variable g // Error, g cannot be used as an expression g() // OK, g can be invoked g // Error, g cannot be used as a return value. }

需要注意的是,捕获具有传递性,如果一个函数 f 调用了捕获 var 变量的函数 g,且 g 捕获的 var 变量不在函数 f 内定义,那么函数 f 同样捕获了 var 变量,此时,f 也不能作为一等公民使用。

以下示例中,g 捕获了 var 声明的变量 xf 调用了 g,且 g 捕获的 x 不在 f 内定义,f 同样不能作为一等公民使用:

func h(){ var x = 1 func g() { x } // captured a mutable variable func f() { g() // invoked g } return f // Error }

以下示例中,g 捕获了 var 声明的变量 xf 调用了 g。但 g 捕获的 xf 内定义,f 没有捕获其它 var 声明的变量。因此,f 仍作为一等公民使用:

func h(){ func f() { var x = 1 func g() { x } // captured a mutable variable g() } return f // Ok }

静态成员变量和全局变量的访问,不属于变量捕获,因此访问了 var 修饰的全局变量、静态成员变量的函数或 lambda 仍可作为一等公民使用。

class C { static public var a: Int32 = 0 static public func foo() { a++ // OK return a } } var globalV1 = 0 func countGlobalV1() { globalV1++ C.a = 99 let g = C.foo // OK } func g(){ let f = countGlobalV1 // OK f() }