簡體   English   中英

內部閉包的捕獲列表是否需要將“self”重新聲明為“weak”或“unowned”?

[英]Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?

如果我有一個閉包傳遞給這樣的函數:

 someFunctionWithTrailingClosure { [weak self] in
     anotherFunctionWithTrailingClosure { [weak self] in 
         self?.doSomething()
     }
 }

如果我宣布自己的[weak self]someFunctionWithTrailingClosure的捕獲列表沒有重新聲明它是weak在捕獲列表中再次anotherFunctionWithTrailingClosure self已經成為一種Optional類型,但它也成為一個weak引用呢?

謝謝!

不需要anotherFunctionWithTrailingClosure[weak self]

您可以憑經驗對此進行測試:

class Experiment {
    func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
        print("starting", #function)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing", #function)
        }
    }

    func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
        print("starting", #function)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing", #function)
        }
    }

    func doSomething() {
        print(#function)
    }

    func testCompletionHandlers() {
        someFunctionWithTrailingClosure { [weak self] in
            self?.anotherFunctionWithTrailingClosure { // [weak self] in
                self?.doSomething()
            }
        }
    }

    // go ahead and add `deinit`, so I can see when this is deallocated

    deinit {
        print("deinit")
    }
}

然后:

func performExperiment() {
    DispatchQueue.global().async {
        let obj = Experiment()

        obj.testCompletionHandlers()

        // sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler

        Thread.sleep(forTimeInterval: 1.5)
    }
}

如果你這樣做,你會看到doSomething永遠不會被調用,並且deinitanotherFunctionWithTrailingClosure調用它的閉包之前anotherFunctionWithTrailingClosure調用。

話雖如此,我可能仍然傾向於在anotherFunctionWithTrailingClosure上使用[weak self]語法來明確我的意圖。

TL; 博士

盡管在外部塊中使用一次[weak self] (EX1),但如果您將此引用更改為 strong(例如, guard let self = self ),則內部塊中也需要一個[weak self] (EX3) )。

同樣在內部塊上只使用一次[weak self]通常是一個錯誤 (EX2_B)。 不幸的是,這是重構代碼時常犯的錯誤,並且在創建時很難發現。


一個好的經驗法則是,如果對象在閉包外立即是強的,則始終使用weak

不保留self示例(即通常這些是“好”場景):

// EX1
fn { [weak self] in
  self?.foo() 
}
// EX2
fn { [weak self] in 
  fn2 {
    self?.foo()
  }
}
// self is weak inside fn, thus adding an extra `[weak self]` inside fn2 is unnecessary
// EX3
fn { [weak self] in 
  guard let self = self else { return }
  fn2 { [weak self] in
    self?.foo()
  }
}

確實保留self示例(即通常是“壞”場景):

// EX1_B
fn {
  self.foo()
}
// fn retains self
// EX2_B
fn {
  fn2 { [weak self] in
    self.foo()
  }
}
// fn retains self (this is a common, hard-to-spot mistake)
// EX3_B
fn { [weak self] in 
  guard let self = self else { return }
  fn2 {
    self.foo()
  }
}
// fn2 retains self

正如Hamish 所提到的weak有用的主要原因有兩個:

  1. 以防止保留循環。
  2. 防止對象的壽命超過應有的壽命。

更多關於#2(防止長壽命對象)

Rob 的示例中,該函數沒有保留閉包(除了 dispatch_async 之外,它幾乎可以保證在將來的某個時刻觸發閉包),因此您永遠不會以保留周期結束。 因此,在這種情況下使用weak是為了防止 #2 發生。

正如 Hamish 所提到的,在這個例子中實際上並不需要弱來防止保留循環,因為沒有保留循環。 在這種情況下, weak用於防止對象存活時間超過所需時間。 這完全取決於您的用例,當您考慮一個對象的壽命超過所需時間時。 因此,有些時候你會想使用weak只能在外面(EX2),以及其他時候,你會想用weak外, strong內心, weak內舞(EX3)為例。

更多關於#1(防止保留循環)

為了檢查保留循環問題,假設一個函數正在存儲對塊(即堆)的引用,而不是直接引用該函數(即堆棧)。 很多時候,我們不知道一個類/函數的內部,所以它的安全假設功能保留塊。

現在您可以使用weak外部輕松創建一個保留循環,並且只使用strong內部(EX3_B):

public class CaptureListExperiment {

    public init() {

    }

    var _someFunctionWithTrailingClosure: (() -> ())?
    var _anotherFunctionWithTrailingClosure: (() -> ())?

    func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting someFunctionWithTrailingClosure")
        _someFunctionWithTrailingClosure = closure

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            self?._someFunctionWithTrailingClosure!()
            print("finishing someFunctionWithTrailingClosure")
        }
    }

    func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting anotherFunctionWithTrailingClosure")
        _anotherFunctionWithTrailingClosure = closure

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            self?._anotherFunctionWithTrailingClosure!()
            print("finishing anotherFunctionWithTrailingClosure")
        }
    }

    func doSomething() {
        print("doSomething")
    }

    public func testCompletionHandlers() {
        someFunctionWithTrailingClosure { [weak self] in
            guard let self = self else { return }
            self.anotherFunctionWithTrailingClosure { // [weak self] in
                self.doSomething()
            }
        }
    }

    // go ahead and add `deinit`, so I can see when this is deallocated

    deinit {
        print("deinit")
    }
}

func performExperiment() {

    let obj = CaptureListExperiment()

    obj.testCompletionHandlers()
    Thread.sleep(forTimeInterval: 1.3)
}

performExperiment()

/* Output:

starting someFunctionWithTrailingClosure
starting anotherFunctionWithTrailingClosure
finishing someFunctionWithTrailingClosure
doSomething
finishing anotherFunctionWithTrailingClosure
*/

請注意,沒有調用deinit ,因為創建了一個保留循環。

這可以通過刪除strong引用(EX2)來解決:

public func testCompletionHandlers() {
    someFunctionWithTrailingClosure { [weak self] in
        //guard let self = self else { return }
        self?.anotherFunctionWithTrailingClosure { // [weak self] in
            self?.doSomething()
        }
    }
}

或者使用弱/強/弱舞(EX3):

public func testCompletionHandlers() {
    someFunctionWithTrailingClosure { [weak self] in
        guard let self = self else { return }
        self.anotherFunctionWithTrailingClosure { [weak self] in
            self?.doSomething()
        }
    }
}

為 Swift 4.2 更新:

public class CaptureListExperiment {

    public init() {

    }

    func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting someFunctionWithTrailingClosure")

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing someFunctionWithTrailingClosure")
        }
    }

    func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting anotherFunctionWithTrailingClosure")

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing anotherFunctionWithTrailingClosure")
        }
    }

    func doSomething() {
        print("doSomething")
    }

    public func testCompletionHandlers() {
        someFunctionWithTrailingClosure { [weak self] in
            guard let self = self else { return }
            self.anotherFunctionWithTrailingClosure { // [weak self] in
                self.doSomething()
            }
        }
    }

    // go ahead and add `deinit`, so I can see when this is deallocated

    deinit {
        print("deinit")
    }
}

試試 Playgorund:

func performExperiment() {

    let obj = CaptureListExperiment()

    obj.testCompletionHandlers()
    Thread.sleep(forTimeInterval: 1.3)
}

performExperiment()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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