簡體   English   中英

避免[弱自我]進行簡單的操作?

[英]Avoiding [weak self] for simple operations?

對於短期運行的操作,避免[weak self]是否可以接受? 例如, URLSession將保留來自dataTask(with:completion:)的閉包:

final class ViewController: UIViewController {
  let label = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()
    URLSession.shared.dataTask(with: url) { data, response, error in
      guard let data = data else { return }
      let decodedString = String(bytes: data, encoding: .utf8)

      DispatchQueue.main.async {
        self.label.text = decodedString
      }
    }.resume()
  }
}

在這種情況下,閉包會強烈地捕獲self ,這意味着即使這個ViewController被閉包保存在內存中。 URLSession將保持關閉直到數據任務完成,這意味着ViewController的生命周期可能會延長,直到dataTask完成。

在這種情況下,我們是否應該使用捕獲列表來避免這種行為? 我的推理是否正確,這里沒有參考周期?

ViewController 的生命周期可能會延長,直到 dataTask 完成

所以問題是這是否一致。 這甚至可能是一件好事。 如果可以,那很好,並且不需要weak self ,因為沒有保留周期,因為 url 會話是共享的。

但是當 url 會話是一個實例屬性並且有一個真正的委托時,事情要復雜得多,你真的可以得到一個保留周期,因為會話保留了它的委托,這可能會保留會話。

如果您擔心引用周期,則在使用 URL 請求時通常不會得到引用周期。 問題是 URL 請求遲早會完成(幾分鍾后)並且您的控制器會被釋放。 引用周期只是暫時的,不會導致內存泄漏。

問題是,即使用戶已經關閉控制器並且不再顯示它,您是否希望將控制器保留在內存中。 它可能不會造成任何問題,但仍然很浪費。 您持有不需要且無法重用的內存。

另請注意,您可能實際上希望在控制器關閉時取消正在運行的請求,以避免發送/接收不再需要的數據。

在我看來,您不應該太擔心參考周期,而應該更多地考慮所有權。 強引用意味着擁有某些東西。 該請求沒有理由“擁有”控制器。 反之亦然——控制器擁有並管理請求。 如果沒有所有權,為了清楚起見,我會使用weak

我的推理是否正確,這里沒有參考周期?

這里沒有引用循環。 ViewController不保留dataTask完成處理程序。 您可以將其視為 iOS 保持對視圖控制器和完成處理程序的強引用,並且完成處理程序也保持對視圖控制器的強引用。 沒有從視圖控制器返回到完成處理程序或任何引用完成處理程序的對象鏈的強引用,因此您是無循環的。 UIView.animate尋找相同的模式,您再次將閉包發送到 iOS,而不是將它們存儲在本地。

對於短期運行的操作,避免[weak self]是否可以接受?

工作的持續時間不是一個因素。 兩個相關的問題是:

  1. 是否存在引用循環?
  2. 引用循環會被打破嗎?

拿這個例子:

class BadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }
}

BadVC在這里設法創建了一個引用循環,一旦加載它的視圖就永遠不會被破壞。 cycleMaker()將在納秒內執行的事實並沒有使我們免於內存泄漏。


務實地說,還有第三個問題:

  1. 這段代碼是否以難以理解、容易破壞或不可靠的方式避免了永久引用循環,因此將來可能會因誤用或修改而出現引用循環?

您可以手動中斷參考循環。 例如:

class StillBadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }

    func breakCycle() {
        cycleMaker = { }
    }
}

在這里,我們處於危險之中,因為StillBadVC有一個對cycleMaker的強引用,而cycleMaker捕獲了對StillBadVC的強引用。 只要有人記得調用breakCycle() ,循環就會中斷,此時視圖控制器將刪除其對cycleMaker強引用,從而允許cycleMaker解除分配。 但是,如果有人忘記調用breakCycle() ,則循環不會中斷。 調用一個叫做breakCycle()的方法通常不是使用視圖控制器的契約的一部分,所以我們預計StillBadVC在實踐中會導致內存泄漏。

你絕對應該在這里使用[weak self] ,不是因為強引用循環的任何風險,而是因為這個閉包的存在僅用於更新標簽。 編寫故意將視圖控制器及其視圖保留在內存中的代碼是沒有意義的,因此您可以更新視圖中可能已被解除且不再可見的標簽。

weak關鍵字的存在不僅僅是為了避免強引用循環,而是為了准確表示對象所有權和管理對象生命周期。 您不應該僅僅為了保存與[weak self]捕獲列表相關的幾個擊鍵而歪曲對象所有權圖。

我想你已經在這里得到了答案。 這不是參考循環。

但是為了建立一種系統的方法,我在這里的建議更簡單。 忘記考慮泄漏和其他東西。

考慮所有權流量控制,然后是內存管理

  • 所有權:這個對象是否需要擁有另一個對象? 一個對象應該擁有它的委托嗎? 子視圖應該擁有它的父視圖嗎? 這個請求是否擁有這個 viewController?
  • 流控制:我想多快取消分配這個對象? 立即或在視圖從屏幕上移除時?
  • 內存管理:這是一個強引用循環嗎?

這個思考過程不僅可以幫助您區分真正的內存泄漏和非泄漏。 它還可以幫助您更好地設計和閱讀您的代碼,而不是盲目地到處轉儲[weak self]

暫無
暫無

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

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