[英]Variable capture by closures in Swift and inout parameters
我注意到當Swift中的閉包捕獲變量時,閉包實際上可以修改該值。 這對我來說似乎很瘋狂,也是獲取可怕錯誤的絕佳方法,特別是當幾個閉包捕獲相同的var時。
var capture = "Hello captured"
func g(){
// this shouldn't be possible!
capture = capture + "!"
}
g()
capture
另一方面,有inout參數,允許函數或閉包修改其參數。
什么是inout的需要,甚至捕獲的變量已經可以修改而不受懲罰?? !!
只是想了解這背后的設計決策......
捕獲的外部作用域的變量不是例程的參數,因此它們的可變性是從上下文繼承的。 默認情況下,例程的實際參數是常量(let),因此無法在本地修改(並且不返回它們的值)
另請注意,您的示例並非真正捕獲capture
因為它是一個全局變量。
var global = "Global"
func function(nonmutable:Int, var mutable:Int, inout returnable:Int) -> Void {
// global can be modified here because it's a global (not captured!)
global = "Global 2"
// nomutable can't be modified
// nonmutable = 3
// mutable can be modified, but it's caller won't see the change
mutable = 4
// returnable can be modified, and it's caller sees the change
returnable = 5
}
var nonmutable = 1
var mutable = 2
var output = 3
function(nonmutable, mutable, &output)
println("nonmutable = \(nonmutable)")
println("mutable = \(mutable)")
println("output = \(output)")
此外,正如您所看到的,inout參數的傳遞方式不同,因此很明顯返回時,值可能不同。
David的回答是完全正確的,但我想我會舉例說明捕獲實際上是如何工作的:
func captureMe() -> (String) -> () {
// v~~~ This will get 'captured' by the closure that is returned:
var capturedString = "captured"
return {
// The closure that is returned will print the old value,
// assign a new value to 'capturedString', and then
// print the new value as well:
println("Old value: \(capturedString)")
capturedString = $0
println("New value: \(capturedString)")
}
}
let test1 = captureMe() // Output: Old value: captured
println(test1("altered")) // New value: altered
// But each new time that 'captureMe()' is called, a new instance
// of 'capturedString' is created with the same initial value:
let test2 = captureMe() // Output: Old value: captured
println(test2("altered again...")) // New value: altered again...
// Old value will always start out as "captured" for every
// new function that captureMe() returns.
結果就是你不必擔心關閉會改變捕獲的值 - 是的,它可以改變它,但僅限於返回閉包的特定實例。 返回閉包的所有其他實例將獲得它們自己的,獨立的捕獲值副本,它們只能由它們改變。
以下是在本地上下文之外捕獲變量的閉包的幾個用例,這可能有助於了解此功能為何有用:
假設您要從數組中過濾重復項。 有一個filter
函數,它接受一個過濾謂詞,並返回一個只有與該謂詞匹配的條目的新數組。 但是如何通過已經看到條目的狀態並因此重復? 你需要謂詞來保持調用之間的狀態 - 你可以通過讓謂詞捕獲一個保存該狀態的變量來實現這一點:
func removeDupes<T: Hashable>(source: [T]) -> [T] {
// “seen” is a dictionary used to track duplicates
var seen: [T:Bool] = [:]
return source.filter { // brace marks the start of a closure expression
// the closure captures the dictionary and updates it
seen.updateValue(true, forKey: $0) == nil
}
}
// prints [1,2,3,4]
removeDupes([1,2,3,1,1,2,4])
確實,您可以使用也帶有inout參數的過濾器函數來復制此功能 - 但是很難編寫一些如此通用但靈活的閉包可能性。 (你可以用reduce
而不是filter
來做這種過濾filter
,因為reduce會從調用到調用通過狀態 - 但filter
版本可能更清晰)
標准庫中有一個GeneratorOf
結構,可以很容易地啟動各種序列生成器。 您使用閉包初始化它,該閉包可以捕獲用於生成器狀態的變量。
假設您想要一個生成器,該生成器提供從0到n范圍內的m個數字的隨機遞增序列。 以下是使用GeneratorOf
:
import Darwin
func randomGeneratorOf(#n: Int, #from: Int) -> GeneratorOf<Int> {
// state variable to capture in the closure
var select = UInt32(n)
var remaining = UInt32(from)
var i = 0
return GeneratorOf {
while i < from {
if arc4random_uniform(remaining) < select {
--select
--remaining
return i++
}
else {
--remaining
++i
}
}
// returning nil marks the end of the sequence
return nil
}
}
var g = randomGeneratorOf(n: 5, from: 20)
// prints 5 random numbers in 0..<20
println(",".join(map(g,toString)))
同樣,沒有閉包可以做這種事情 - 在沒有它們的語言中,你可能有一個生成器協議/接口,並創建一個持有狀態的對象,並有一個提供值的方法。 但是閉合表達式允許以最小的鍋爐板來實現這一點。
能夠修改外部作用域中捕獲的變量的閉包在各種語言中非常常見。 這是C#,JavaScript,Perl,PHP,Ruby,Common Lisp,Scheme,Smalltalk等許多默認行為。 這也是在Objective-C的行為,如果外部變量是__block
,在Python 3如果外變量是nonlocal
,在C ++如果外變量與捕獲&
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.