简体   繁体   English

iOS NotificationCenter 意外保留关闭

[英]iOS NotificationCenter unexpected retained closure

In the documentation , it says:文档中,它说:

The block is copied by the notification center and (the copy) held until the observer registration is removed.该块由通知中心复制并(副本)保留,直到观察者注册被删除。

And it provides a one-time observer example code like so:它提供了一个一次性观察者示例代码,如下所示:

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!)
}

Now I expect the observer to be removed as removeObserver(_:) is called, so my code goes like this:现在我希望在调用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)
}

Surprisingly, the self is retained and not removed.令人惊讶的是, self被保留而不是被移除。

What is going on?到底是怎么回事?

Confirmed some weird behavior going on.确认发生了一些奇怪的行为。

First, I put a breakpoint on the success observer closure, before observers are removed, and printed the memory address of tokens, and NotificationCenter.default .首先,在观察者被移除之前,我在成功观察者闭包上放置了一个断点,并打印了令牌的内存地址和NotificationCenter.default Printing NotificationCenter.default shows the registered observers.打印NotificationCenter.default显示注册的观察者。

I won't post the log here since the list is very long.我不会在这里发布日志,因为列表很长。 By the way, self was captured weakly in the closures.顺便说一句, self在闭包中被弱捕获。

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

Also confirmed that observers were (supposedly) removed by printing NotificationCenter.default again after the removeObserver(_:) s were invoked.还通过在调用removeObserver(_:)后再次打印NotificationCenter.default确认观察者(据说)被删除。

Next, I left the view controller and confirmed that the self in the quote code was deallocated.接下来,我离开了视图控制器并确认引用代码中的self已被释放。

Finally, I turned on the debug memory graph and searched for the memory addresses and found this:最后,我打开调试内存图并搜索内存地址,发现了这个:

在此处输入图片说明

In the end, there was no retain cycle.最后,没有保留周期。 It was just that the observers were not removed, and because the closures were alive, the captured self was alive beyond its life cycle.只是观察者没有被移除,并且因为闭包是活着的,被捕获的self在其生命周期之外还活着。

Please comment if you guys think this is a bug.如果你们认为这是一个错误,请发表评论。 According to the documentation on NotificationCenter , it most likely is...根据NotificationCenter上的文档,它很可能是...

Recently I've run into similar problem myself.最近我自己也遇到了类似的问题。

This does not seem a bug, but rather undocumented feature of the token which (as you've already noticed) is of __NSObserver type.这似乎不是错误,而是令牌的未记录功能(正如您已经注意到的)是 __NSObserver 类型。 Looking closer at that type you can see that it holds the reference to a block. 仔细观察该类型,您可以看到它包含对块的引用。 Since your blocks hold strong reference to the token itself (through optional var), you have a cycle.由于您的块持有对令牌本身的强引用(通过可选的 var),因此您有一个循环。

Try to set the optional token reference to nil once it is used:尝试将可选令牌引用设置为 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)
}

You need to use weak reference for self like this :您需要像这样为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