2017-06-20 30 views
2

我知道有幾個相關的問題,而且我可以在互聯網上找到很多帖子。 但是,我不明白封閉可以持有引用的事實。在參考類型的情況下,這是完全正常且非常合理的,但值類型如何,包括structenum? 請參閱此代碼。Swift:閉包是否有對常量或變量的引用?

let counter:() -> Int 
var count = 0 
do { 
    counter = { 
     count += 1 
     return count 
    } 
} 
count += 1 // 1 
counter() // 2 
counter() // 3 

我們可以通過兩種方式訪問​​值類型count。一種是直接使用count,另一種是通過關閉counter。 但是,如果我們寫

let a = 0 
let b = a 

,在存儲b當然有不同的面積a,因爲他們是值類型。並且這種行爲是與參考類型不同的值類型的明顯特徵。 然後回到閉包主題,閉包有對值類型的變量或常量的引用。

那麼,我可以說值類型的功能,我們不能有任何引用的值類型改變的情況下封閉的捕獲值? 對我來說,捕獲對值類型的引用是非常令人驚訝的,同時我上面顯示的經驗表明這一點。

你能解釋這件事嗎?

+2

我不確定你想獲得多少技術細節,但https://stackoverflow.com/a/40979548/2976878&https://stackoverflow.com/q/43171341/2976878可能是有用。基本上,捕獲的值被放入堆中的引用計數框中,然後存儲在函數值的上下文對象中。當涉及到捕獲具有引用類型的變量時,引用本身被放入堆分配框(考慮引用*本身*是值類型),所以您實際上是對引用的引用。 – Hamish

+0

我看過你的文章,這很刺激。我瞭解內容。但是,什麼是參考計數盒?我讀了你多次展示的答案,但我沒有見過這個詞。你能解釋一下嗎? –

+0

很高興你發現它很有用!一個盒子只是一個給定值的包裝,所以在閉包捕獲時,捕獲的變量的值在結構中堆積爲「盒子」,並在我的答案中包含專門的「盒子'結構的佈局。然後(主要)對該變量的任何未來訪問(在其定義的範圍內以及閉包中)僅被編譯爲訪問堆上的裝箱值。 – Hamish

回答

1

我認爲混亂是由於太過努力的價值類型與參考類型。這與此無關。讓我們將數字作爲參考類型:

class RefInt: CustomStringConvertible { 
    let value: Int 
    init(value: Int) { self.value = value } 
    var description: String { return "\(value)" } 
} 

let counter:() -> RefInt 
var count = RefInt(value: 0) 
do { 
    counter = { 
     count = RefInt(value: count.value + 1) 
     return count 
    } 
} 
count = RefInt(value: count.value + 1) // 1 
counter() // 2 
counter() // 3 

這種感覺有什麼不同嗎?我希望不是。這是同樣的事情,只是在參考。這不是一個價值/參考的東西。

問題是,正如你注意到的那樣,閉包捕獲變量。不是變量的值,或變量指向的引用的值,而是變量本身)。因此,在所有其他捕獲該變量的位置(包括調用者)中都會看到對閉包內變量的更改。在Capturing Values中對此進行了更全面的討論。如果你有興趣(我現在進入有點技術性,可能會超出你在乎什麼,現在的)

深一點:

瓶蓋居然有變量的引用,並改變它們會立即發生,包括調用didSet等。這與inout參數不同,該參數僅在返回時纔將值分配給其原始上下文。你可以看到,這種方式:

let counter:() -> Int 
var count = 0 { 
    didSet { print("set count") } 
} 

do { 
    counter = { 
     count += 1 
     print("incremented count") 
     return count 
    } 
} 

func increaseCount(count: inout Int) { 
    count += 1 
    print("increased Count") 
} 

print("1") 
count += 1 // 1 
print("2") 
counter() // 2 
print("3") 
counter() // 3 

increaseCount(count: &count) 

這版畫:「增加數量」

1 
set count 
2 
set count 
incremented count 
3 
set count 
incremented count 
increased Count 
set count 

注「設定計數」怎麼總是前「增加的計」,但後這使得閉包真正引用了它們捕獲的同一個變量(而不是值或引用;變量),以及爲什麼我們稱它爲捕獲閉包,而不是「傳遞」到函數。 (當然,您也可以「通過」關閉,在這種情況下,它們的行爲與這些參數的功能完全相同。)

+0

謝謝你的回答!但是,我仍然不清楚。 「封閉捕獲變量」是什麼意思?我無法想象這很好。如你所知,''''''實例的變量包含實例的內存地址並且位於堆棧區域。另一方面,與值類型對應的變量是值本身,也在堆棧區域中。閉包捕獲什麼時閉包有什麼作用?你解釋了變量本身,但是如果你給我更詳細的解釋,封閉是如何產生的? –

+0

有關實施討論,請參閱上面他評論中的Hamish鏈接。 (捕獲的變量被放置在堆的參考框中。)但重要的區別是「與值類型對應的變量」不是*「值本身」。它是一個名稱,*表示一個值,可以改變它指向的值。這個非常重要。如果它是「價值本身」,那麼var就沒有意義。 ('let'可以這樣想,只是一個「綁定」而不是一個引用,當所有的東西都是'let'時,這些問題中的大多數都會消失。) –

+0

變量('var')是一個值的引用,而不是價值本身。 「參考」這個詞被用於許多事情,這可能會讓人困惑。例如,僅僅因爲某些東西是「價值類型」並不意味着它具有「價值語義」,反之亦然。例如,'NSDate'是一個引用類型,但是具有值語義。用引用語義構建一個結構(值類型)是相當容易的。無論這些東西是堆棧還是堆,都完全是一個實現細節(並且Swift在堆棧和堆之間移動了一些東西)。某些類型不在堆棧中或堆中(標記的指針)。 –