[英]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
永遠不會被調用,並且deinit
在anotherFunctionWithTrailingClosure
調用它的閉包之前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
有用的主要原因有兩個:
在Rob 的示例中,該函數沒有保留閉包(除了 dispatch_async 之外,它幾乎可以保證在將來的某個時刻觸發閉包),因此您永遠不會以保留周期結束。 因此,在這種情況下使用weak
是為了防止 #2 發生。
正如 Hamish 所提到的,在這個例子中實際上並不需要弱來防止保留循環,因為沒有保留循環。 在這種情況下, weak
用於防止對象存活時間超過所需時間。 這完全取決於您的用例,當您考慮一個對象的壽命超過所需時間時。 因此,有些時候你會想使用weak
只能在外面(EX2),以及其他時候,你會想用weak
外, strong
內心, weak
內舞(EX3)為例。
為了檢查保留循環問題,假設一個函數正在存儲對塊(即堆)的引用,而不是直接引用該函數(即堆棧)。 很多時候,我們不知道一個類/函數的內部,所以它的安全假設功能保留塊。
現在您可以使用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.