簡體   English   中英

iOS NotificationCenter 意外保留關閉

[英]iOS NotificationCenter unexpected retained closure

文檔中,它說:

該塊由通知中心復制並(副本)保留,直到觀察者注冊被刪除。

它提供了一個一次性觀察者示例代碼,如下所示:

let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
var token: NSObjectProtocol?
token = center.addObserverForName("OneTimeNotification", object: nil, queue: mainQueue) { (note) in
    print("Received the notification!")
    center.removeObserver(token!)
}

現在我希望在調用removeObserver(_:)移除觀察者,所以我的代碼是這樣的:

let nc = NotificationCenter.default
var successToken: NSObjectProtocol?
var failureToken: NSObjectProtocol?

successToken = nc.addObserver(
    forName: .ContentLoadSuccess,
    object: nil,
    queue: .main)
{ (_) in
    nc.removeObserver(successToken!)
    nc.removeObserver(failureToken!)

    self.onSuccess(self, .contentData)
}

failureToken = nc.addObserver(
    forName: .ContentLoadFailure,
    object: nil,
    queue: .main)
{ (_) in
    nc.removeObserver(successToken!)
    nc.removeObserver(failureToken!)

    guard case .failed(let error) = ContentRepository.state else {
        GeneralError.invalidState.record()
        return
    }

    self.onFailure(self, .contentData, error)
}

令人驚訝的是, self被保留而不是被移除。

到底是怎么回事?

確認發生了一些奇怪的行為。

首先,在觀察者被移除之前,我在成功觀察者閉包上放置了一個斷點,並打印了令牌的內存地址和NotificationCenter.default 打印NotificationCenter.default顯示注冊的觀察者。

我不會在這里發布日志,因為列表很長。 順便說一句, self在閉包中被弱捕獲。

Printing description of successToken:
▿ Optional<NSObject>
  - some : <__NSObserver: 0x60000384e940>
Printing description of failureToken:
▿ Optional<NSObject>
  - some : <__NSObserver: 0x60000384ea30>

還通過在調用removeObserver(_:)后再次打印NotificationCenter.default確認觀察者(據說)被刪除。

接下來,我離開了視圖控制器並確認引用代碼中的self已被釋放。

最后,我打開調試內存圖並搜索內存地址,發現了這個:

在此處輸入圖片說明

最后,沒有保留周期。 只是觀察者沒有被移除,並且因為閉包是活着的,被捕獲的self在其生命周期之外還活着。

如果你們認為這是一個錯誤,請發表評論。 根據NotificationCenter上的文檔,它很可能是...

最近我自己也遇到了類似的問題。

這似乎不是錯誤,而是令牌的未記錄功能(正如您已經注意到的)是 __NSObserver 類型。 仔細觀察該類型,您可以看到它包含對塊的引用。 由於您的塊持有對令牌本身的強引用(通過可選的 var),因此您有一個循環。

嘗試將可選令牌引用設置為 nil 使用后:

let nc = NotificationCenter.default
var successToken: NSObjectProtocol?
var failureToken: NSObjectProtocol?

successToken = nc.addObserver(
    forName: .ContentLoadSuccess,
    object: nil,
    queue: .main)
{ (_) in
    nc.removeObserver(successToken!)
    nc.removeObserver(failureToken!)

    successToken = nil // Break reference cycle
    failureToken = nil

    self.onSuccess(self, .contentData)
}

您需要像這樣為self使用弱引用:

let nc = NotificationCenter.default
var successToken: NSObjectProtocol?
var failureToken: NSObjectProtocol?

successToken = nc.addObserver(
    forName: .ContentLoadSuccess,
    object: nil,
    queue: .main)
{[weak self] (_) in

    guard let strongSelf = self else { return }

    nc.removeObserver(successToken!)
    nc.removeObserver(failureToken!)

    strongSelf.onSuccess(strongSelf, .contentData)
}

failureToken = nc.addObserver(
    forName: .ContentLoadFailure,
    object: nil,
    queue: .main)
{[weak self] (_) in

    guard let strongSelf = self else { return }

    nc.removeObserver(successToken!)
    nc.removeObserver(failureToken!)

    guard case .failed(let error) = ContentRepository.state else {
        GeneralError.invalidState.record()
        return
    }

    strongSelf.onFailure(strongSelf, .contentData, error)
}

暫無
暫無

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

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