值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
举个例子,这有一个叫做 makeIncrementer
的函数,其包含了一个叫做 incrementer
的嵌套函数。嵌套函数 incrementer()
从上下文中捕获了两个值,runningTotal
和 amount
。捕获这些值之后,makeIncrementer
将 incrementer
作为闭包返回。每次调用 incrementer
时,其会以 amount
作为增量增加 runningTotal
的值。
funcmakeIncrementer(forIncrementamount: Int) -> () ->Int { var runningTotal =0 funcincrementer() ->Int { runningTotal += amount return runningTotal } return incrementer }
makeIncrementer
返回类型为 () -> Int
。这意味着其返回的是一个 函数 ,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 Int
类型的值。关于函数返回其他函数的内容,请查看 函数类型作为返回类型。
makeIncrementer(forIncrement:)
函数定义了一个初始值为 0
的整型变量 runningTotal
,用来存储当前总计数值。该值为 incrementer
的返回值。
makeIncrementer(forIncrement:)
有一个 Int
类型的参数,其外部参数名为 forIncrement
,内部参数名为 amount
,该参数表示每次 incrementer
被调用时 runningTotal
将要增加的量。makeIncrementer
函数还定义了一个嵌套函数 incrementer
,用来执行实际的增加操作。该函数简单地使 runningTotal
增加 amount
,并将其返回。
如果我们单独考虑嵌套函数 incrementer()
,会发现它有些不同寻常:
funcincrementer() ->Int { runningTotal += amount return runningTotal }
incrementer()
函数并没有任何参数,但是在函数体内访问了 runningTotal
和 amount
变量。这是因为它从外围函数捕获了 runningTotal
和 amount
变量的 引用 。捕获引用保证了 runningTotal
和 amount
变量在调用完 makeIncrementer
后不会消失,并且保证了在下一次执行 incrementer
函数时,runningTotal
依旧存在。
注意
为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。
Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
下面是一个使用 makeIncrementer
的例子:
let incrementByTen =makeIncrementer(forIncrement:10)
该例子定义了一个叫做 incrementByTen
的常量,该常量指向一个每次调用会将其 runningTotal
变量增加 10
的 incrementer
函数。调用这个函数多次可以得到以下结果:
incrementByTen() // 返回的值为10 incrementByTen() // 返回的值为20 incrementByTen() // 返回的值为30
如果你创建了另一个 incrementer
,它会有属于自己的引用,指向一个全新、独立的 runningTotal
变量:
let incrementBySeven =makeIncrementer(forIncrement:7) incrementBySeven() // 返回的值为7
再次调用原来的 incrementByTen
会继续增加它自己的 runningTotal
变量,该变量和 incrementBySeven
中捕获的变量没有任何联系:
incrementByTen() // 返回的值为40
注意
如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考 闭包引起的循环强引用。