iOS NotificationCenter意外保留关闭

时间:2019-06-27 06:39:47

标签: ios swift nsnotificationcenter notificationcenter addobserver

documentation中,它表示:

  

该块将由通知中心复制并保留(副本)   直到观察者注册被删除。

它提供了一个一次性的观察者示例代码,如下所示:

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被保留而不被删除。

这是怎么回事?

3 个答案:

答案 0 :(得分:0)

确认正在发生一些奇怪的行为。

首先,在删除观察者之前,我在成功观察者关闭处设置了一个断点,并打印了令牌和NotificationCenter.default的内存地址。打印NotificationCenter.default将显示已注册的观察者。

由于列表很长,因此我不会在此处发布日志。 顺便说一下,self在关闭中被弱捕获了。

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

还确认(据说)通过调用NotificationCenter.default之后再次打印removeObserver(_:)删除了观察者。

接下来,我离开视图控制器,并确认已取消分配报价代码中的self

最后,我打开调试内存图并搜索内存地址,发现了这一点:

enter image description here

最后,没有保留周期。只是观察者没有被移除,并且因为关闭是有效的,所以捕获的self在其生命周期之外仍然有效。

如果你们认为这是一个错误,请发表评论。根据{{​​1}}上的文档,很可能是...

答案 1 :(得分:0)

最近,我本人也遇到了类似的问题。

这似乎不是一个错误,而是令牌的未记录的功能(如您已经注意到的)是__NSObserver类型的。 Looking closer at that type,您会看到它包含对块的引用。由于您的代码块(通过可选的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)
}

答案 2 :(得分:-1)

您需要对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)
}