Swift 與 Objective-C

Swift 閉包捕捉:變數、快照與物件參考

比較一般變數捕捉、捕捉清單與weak,並儲存一個escaping回呼,在註冊後執行一次,確認值與所有權的差異。

閱讀 2 分鐘
Swift 閉包捕捉:變數、快照與物件參考 封面圖

捕捉清單固定值不等於深層複製物件。整數會保留建立閉包當下的值;類別參考仍指向同一物件。weak是另一種所有權選擇,不會讓目標保持存活。

原理與設計判斷

捕捉可變區域變數的閉包可以觀察後續變動。[number]這類捕捉清單會保留建立時的值。若將閉包參數儲存到函式返回後使用,需要@escaping,但它本身不代表非同步執行或記憶體洩漏。 第一手資料

重現程式碼

同一函式內建立一般捕捉與數值快照後,把number改為2;捕捉類別參考後,把物件值改為3;再釋放weak捕捉的物件。Queue儲存回呼,執行一次後清除,讓保存期間與呼叫時點分開。

這個獨立範例已於 2026-10-23 使用 Apple Swift 6.4,以 Swift 6 語言模式在 arm64 macOS 目標環境執行。不需要網路或額外套件。

下載完整 Swift 檔案

請在下載檔案的資料夾執行。檢查不符時,precondition 會中止程式。請勿使用 -Ounchecked,因為它會移除這些檢查。

swift -swift-version 6 swift-closure-capture-weak-self-escaping.swift
final class Box { var value = 1 }
final class Queue {
    private var action: (() -> Int)?
    func enqueue(_ action: @escaping () -> Int) { self.action = action }
    func run() -> Int? {
        let current = action
        action = nil
        return current?()
    }
}

func experiment() {
    var number = 1
    let live = { number }
    let snapshot = { [number] in number }
    number = 2
    precondition(live() == 2 && snapshot() == 1)

    let box = Box()
    let capturedReference = { [box] in box.value }
    box.value = 3
    precondition(capturedReference() == 3)

    var temporary: Box? = Box()
    let weakRead = { [weak box = temporary] in box?.value }
    precondition(weakRead() == 1)
    temporary = nil
    precondition(weakRead() == nil)

    let queue = Queue()
    queue.enqueue(snapshot)
    precondition(queue.run() == 1 && queue.run() == nil)
    print("variable=2 snapshot=1 captured-object=3 weak-after-release=nil queued=1 then=nil")
}
experiment()

執行結果與解讀

variable=2 snapshot=1 captured-object=3 weak-after-release=nil queued=1 then=nil

一般捕捉回傳2,數值快照回傳1,捕捉的物件參考看到修改後的3。weak在目標釋放後回傳nil。Queue第一次回傳1,第二次回傳nil。此Queue是同步的儲存示範,不是非同步排程器。

適用範圍與限制

捕捉不一定造成堆積配置,編譯器可能最佳化儲存方式。[weak self]不是每個閉包的固定前綴,還要判斷擁有者消失後是否允許略過工作。確認循環時應檢查直接與間接的所有權路徑。

接下來要確認的事

記錄誰持有回呼、何時清除,以及取消的意義。如果工作必須讓擁有者存活,就不要機械式改用弱參考。真正的所有權循環如何切斷,可參考ARC實驗。

延伸實驗

來源與驗證