簡體   English   中英

如果他們引用的對象被釋放,守衛會讓`self` = self 在使用[weak self] 的閉包中導致崩潰嗎?

[英]Could a guard let `self` = self inside a closure that uses [weak self] cause a crash if the object they reference becomes deallocated?

我查看了一些關於使用 [weak self] 和 [unowned self] 堆棧溢出問題的評論。 我需要確保我理解正確。

我正在使用最新的 Xcode - Xcode 13.4,最新的 macOS - macOS Monterey 12.4,並且我正在編寫與 iOS 12.0 兼容到最新 iOS 15 的代碼。

首先,我是否糾正了對它有強引用的內存由強引用所屬的任何一個擁有?

另外,這是我真正需要知道的,我想知道如果我在閉包的開頭使用[weak self] in會發生什么,然后我有語句guard let `self` = self else { return } . 我相信保護語句,如果條件成功,將引用 self 分配給在保護語句的let `self部分中新聲明的名為 self 的強引用。 那是對的嗎?

讓我問的是,關閉結束時會發生什么。 我是否正確,即使較新的 self 擁有強引用,一旦閉包中的代碼執行了閉包中的最后一條語句,強引用指向的內存就會被釋放,因為具有強引用的較新的 self 在閉包本身,並與為該閉包分配的所有內存一起被釋放。

我想我做對了這一切。 如果沒有,請告訴我。

如果您願意,我對任何其他信息或任何澄清感興趣。

如果閉包持有強引用,只要閉包仍然可以訪問,它就會阻止 ARC 釋放對象。

weakunowned的自我允許解決這種強大的所有權
但這意味着閉包的弱自身引用的對象可以被取消初始化,而閉包仍然可以被調用。 在這種情況下,閉包的 self 將引用 nil。

guard實際上只是檢查引用是否仍然有效。 無論如何,一個 nil 都會讓它失敗。 如果它是一個有效的參考,它不會改變捕獲列表; 您可以假設 self 將保持有效,直到當前執行的閉包返回。

實驗證明

這里有一些代碼來試驗差異,帶有一個跟蹤初始化和取消初始化的類,並且可以返回一個帶有self捕獲的閉包:

class T {
    var id:String
    init(_ name:String){
        id = name
        print ("INIT T object \(id)")
    }
    func getClosure() -> (Int)->Int {
        return { [weak self] (i:Int) in    
            guard let self = self else {
                print ("Instance vanished")
                return -1
            }
            print (i,self.id)
            return 0
        }
    }
    deinit {
        print ("DEINIT T object \(id)")
    }
}

讓我們創建一個戲劇性的環境,使閉包可以在創建它的實例中存活:

var oops:(Int)->Int = {(Int)->Int in 0}

func f1() {
    var t = T("f1_test")
    t.getClosure()(32)       // closure is disposed at the end of the function
}                            // t will be deinitialized in any case
func f2() {
    var t = T("f2_test")
    oops = t.getClosure()    // closure will be kept outside of scope
}                            // t can be disposed only if capture is not strong

f1()
f2()
oops(33)

讓我們分析一下會發生什么。 f1()將導致T實例將被取消初始化。 這是因為離開f1時不再引用閉包,所以捕獲的self也不再被引用,ARC 將終止它:

INIT T object f1_test
32 f1_test
DEINIT T object f1_test

f2()調用更加微妙,因為T實例在離開f2時仍然被閉包引用,並且閉包在全局變量中存在。

如果捕獲很強大,則不需要guard ,因為 ARC 會確保實例仍然可用。 您會看到 T 實例不會被取消初始化。

但是由於捕獲是weak的,從f2返回時沒有對T實例的強引用,它會被正確地取消初始化。 所以守衛會讓你得到:

INIT T object f2_test
DEINIT T object f2_test
Instance vanished

這證明了guard let self = self並沒有將閉包的弱捕獲替換為強捕獲。

當您編寫這樣的閉包時:

{ [weak self] in
    guard let self = self else { return }
    ...
}

您正在檢查聲明此閉包的類是否仍被分配,如果該類仍被分配,您將在閉包本身內創建對它的新強引用。

您這樣做是因為如果刪除了對該類的所有其他強引用,那么仍然會在

guard let self = self else { return }

此時,您可以確定在閉包結束之前,聲明閉包的類被分配,因為引用在閉包本身中。

所以,回答你的問題,不,如果你寫

guard let self = self else { return }

該關閉不會崩潰,因為您有一個仍然存在的強引用。

不同的是,如果您使用 [unowned self]。 Unowned self 就像一個未包裝的可選項。 如果你使用它而不檢查它的存在,你就是在隱式地寫

self!.<something>

因此,如果在調用該閉包時釋放了 self ,它將崩潰。

暫無
暫無

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

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