簡體   English   中英

通過Swift和inout參數中的閉包進行變量捕獲

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM