簡體   English   中英

Memory 管理和取消引用 Swift 中的 Object

[英]Memory Management and Dereferencing Object in Swift

我正在創建一個 object,我稱之為在由按鈕控制的 Boolean 循環中運行 function。 創建 object 后,我調用它的兩個函數,以便我可以獲得返回值並將它們傳遞給下一個 function。 一旦循環完成,object 仍然被引用,因此它不會被 ARC 設置為零,因此我創建了 memory 泄漏。 我正在嘗試使用weak引用類型來解決問題,但是當我嘗試 object 時,仍然會被引用。 我將在不更改變量類型或創建可選運算符的情況下粘貼泄漏的代碼,以便您可以看到我從哪里開始。

self.buttonpress = true

DispatchQueue.global().async{
    while buttonpress == true{
        let varfind = find()
        let b = varfind.build()
        let yes = varfind.isInside(index: b,xaxis: locationViewModel.userXaxis, yaxis: locationViewModel.userYaxis)
        self.final = String(Int(locationViewModel.userZaxis)-yes)
    }
}

問題是,我如何讓 while 循環中的內容執行並取消引用 object 及其指針,以便它不會越來越多地占用 memory 並使用弱變量類型崩潰?

我知道我需要使用let varfind: find? = find() let varfind: find? = find() ,然后在 find class 中添加一個deinit以便將所有內容設置為 nil 但是,我認為在將 var yes 中的參數設置為 nil 時存在誤解,例如索引是[String]所以它只能接受一個空數組?

幾點觀察:

  1. 在我們開始討論在while循環中創建的對象之前,我們應該認識到在將該代碼分派到全局隊列的 object 和正在分派的代碼之間存在強引用循環。 有人會在閉包中使用[weak self]模式來打破這個循環。 您可以使用 Duncan 建議的模式,也可以只使用nil -coalescing 運算符:

     DispatchQueue.global().async { [weak self] in while self?.buttonPressed == true { let foo = Foo() foo.doSomething() } }
  2. 現在,讓我們轉向在while循環中創建的對象。 你假設:

    一旦循環完成 object 仍然被引用

    這是極不可能的。 即使這是問題所在,它是while循環中的局部變量這一事實絕對不是問題。 如果您不小心在某處引入了強引用循環,您的對象可能不會被釋放(參見下面的第 3 點)。 或者它可能根本不是您的 class,而是您的函數(或您的函數調用的 API)引入的自動釋放對象(參見下面的第 4 點)。 它可能是底層 API 提供的緩存。 它可以是許多事情中的任何一個。

  3. 但是,假設您想確認您自己的對象是否被釋放。 判斷是否存在強引用循環的方法是“Debug Memory Graph” 為了說明該技術,讓我創建一個引入強引用循環的示例:

     DispatchQueue.global().async { [weak self] in while self?.buttonPressed == true { let foo = Foo() foo.bar(with: foo) } }

    在哪里

    class Foo { var foo: Foo? // bad: this introduces strong reference cycle func bar(with foo: Foo) { self.foo = foo } }

    然后我可以通過點擊底部欄中的“Debug Memory Graph”按鈕來查看 memory 圖表:

    在此處輸入圖像描述

    在左側面板中查找您認為不應再位於 memory 中的對象。 更好的是,看看它們旁邊是否有運行時錯誤三角形。

    在這個例子中,我可以看到許多Foo實例旁邊有運行時錯誤三角形。 當我點擊一個時,我可以在主面板中直觀地看到強引用循環。 而且,因為我打開了“Product” » “Scheme” » “Edit Scheme...” » “Run” » “Diagnostics” » “Malloc Stack Logging”,我什至可以在右側面板中看到強引用的建立位置.

    因此,運行您的應用程序,使用調試 memory 圖表,並查看您的實例是否在左側面板中,如果有,有多少。 對於while循環的每次迭代,是否真的有一個實例沒有被釋放? 是不是while循環沒有停止(參見上面的第 1 點)? 或者您根本沒有看到您的對象,而是有其他東西正在消耗 memory(請參閱以下幾點)?

  4. 已經向您展示了如何檢查您的 memory 圖表以確認對象是否沒有被釋放(例如通過強引用循環),很有可能這根本不是問題。 (在沒有MCVE的情況下,我們無法確定。)

    例如,另一個候選問題可能是自動釋放對象。 例如,考慮:

     DispatchQueue.global().async { [weak self] in while self?.buttonPressed == true { let foo = Foo() foo.bar() } }

    在哪里

    class Foo { var foo: Foo? func bar() { let string = NSString(format: "%d", Int.random(in: 0..<1_000_000)) print(string) } }

    當你運行它時,它將產生一個 memory 圖,如下所示:

    在此處輸入圖像描述

    這里的問題不是Foo ,而是bar方法正在創建一個自動釋放 object。 這可以通過在while循環中放置一個autoreleasepool來解決:

     DispatchQueue.global().async { [weak self] in while self?.buttonPressed == true { autoreleasepool { let foo = Foo() foo.bar() } } }

    值得注意的是,此autoreleasepool池問題僅適用於您或您正在調用的 API 實際上正在創建自動釋放對象的情況。 在 Swift 中,這種情況越來越少見,但有時我們會調用在后台使用自動釋放對象的第三方庫。

  5. 還有許多其他模式可能導致 memory 肆無忌憚地增長。 例如,可能有永遠不會失效的URLSession實例。 或者可能正在發生一些緩存(由您明確或隱藏在您正在調用的任何 API 中)。 或者,您可能有一些帶有一些 CoreFoundation 泄漏或其他非托管對象的代碼。

    如果沒有可重現的問題示例,就不可能說出問題所在。 Instruments 的“分配”工具是一種出色的(如果非常復雜)方法,可以准確地確定哪些內容被泄露或遺棄,但如果您可以向我們展示一個可重現的問題示例並且我們可以指出您可能會更容易正確的方向。

最重要的是,問題不是由while循環中的局部變量引起的。 要么你有一些強引用循環,要么有其他泄露、廢棄或緩存的 memory。 使用“調試 Memory 圖表”將有助於確定是否實際上是您的對象導致了 memory 圖表,或者它是否是一些更微妙的 memory 問題。


與手頭的問題無關,但是這個從多個線程訪問buttonpressfinal的代碼示例不是線程安全的。 線程清理程序 (TSAN)可能有助於識別這些問題。 從多個線程訪問這些屬性時,您應該使用鎖或其他同步機制。 或者實施完全消除這些數據競爭的模式。 但這超出了這個問題的 scope 。 首先解決您的 memory 問題,然后解決線程安全問題。

你有一些誤解。

您當前的代碼:

  • 在后台線程上運行一段代碼。
  • 在該線程內,使用 while 語句重復循環。
  • 在 while 語句中,您創建了一個新的find object(假設find()是一個初始化程序。名稱應該是Find以遵循 Swift 命名約定...)

事情是這樣的:

每次通過 while 循環時,您都會創建一個新的find object,在該find object 上調用幾個方法,然后獲得一個String結果並將其安裝在self.final中。

每次通過 while 循環完成后,scope 中的局部變量 go 以及這些變量持有的任何對象都會被釋放。 因此,您不應該遇到 memory 問題。

您說“我知道我需要使用 let varfind: find? = find(),然后在 find class 中添加一個 deinit,以便將所有內容設置為 nil”。 那是不對的。 由於您在循環內創建了一個find object,因此每個find將在循環完成時被釋放。 此外,當find object 被釋放時,它擁有的對象也將被釋放。

您的代碼中的一個問題是該塊捕獲自我。 只要 while 循環運行,您就會阻止調用此代碼的 object 被釋放。 這可能是也可能不是問題。 要解決這個問題,您應該添加一個“捕獲列表”,將引用傳遞給 self.

另一點:我認為buttonPress是視圖 controller 的實例變量? 如果是這樣,你需要添加self? 對於那個while語句:

DispatchQueue.global().async{ [weak self] in //"[weak self]" creates a capture list
    while let self = self, // Make sure self isn't nil 
          self.buttonpress == true { 
        let varfind = find()
        let b = varfind.build()
        let yes = varfind.isInside(index: b,xaxis: locationViewModel.userXaxis, yaxis: locationViewModel.userYaxis)
        self.final = String(Int(locationViewModel.userZaxis)-yes)
    }
}

暫無
暫無

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

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