iOS NotificationCenter 意外保留关闭
iOS NotificationCenter unexpected retained closure
在 documentation 中,它说:
The block is copied by the notification center and (the copy) held
until the observer registration is removed.
并且它提供了一个一次性观察者示例代码,如下所示:
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
被保留了下来,并没有被删除。
这是怎么回事?
您需要像这样为 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)
}
确认发生了一些奇怪的行为。
首先,我在成功的观察者闭包上打了一个断点,在观察者被移除之前,打印了令牌的内存地址,NotificationCenter.default
。打印 NotificationCenter.default
显示注册的观察员。
我不会post这里的日志,因为列表很长。
顺便说一句,self
在闭包中被弱捕获。
Printing description of successToken:
▿ Optional<NSObject>
- some : <__NSObserver: 0x60000384e940>
Printing description of failureToken:
▿ Optional<NSObject>
- some : <__NSObserver: 0x60000384ea30>
还确认在 removeObserver(_:)
被调用后再次打印 NotificationCenter.default
观察者(据推测)被删除。
接下来,我离开视图控制器并确认引用代码中的 self
已被释放。
最后,我打开调试内存图并搜索内存地址,发现了这个:
最后没有retain cycle。只是观察者没有被移除,因为闭包是活的,所以捕获的 self
在其生命周期之外是活的。
如果你们认为这是一个错误,请发表评论。根据 NotificationCenter
上的文档,它很可能是...
最近我自己 运行 遇到了类似的问题。
这似乎不是错误,而是令牌的未记录特征(正如您已经注意到的那样)属于 __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)
}
在 documentation 中,它说:
The block is copied by the notification center and (the copy) held until the observer registration is removed.
并且它提供了一个一次性观察者示例代码,如下所示:
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
被保留了下来,并没有被删除。
这是怎么回事?
您需要像这样为 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)
}
确认发生了一些奇怪的行为。
首先,我在成功的观察者闭包上打了一个断点,在观察者被移除之前,打印了令牌的内存地址,NotificationCenter.default
。打印 NotificationCenter.default
显示注册的观察员。
我不会post这里的日志,因为列表很长。
顺便说一句,self
在闭包中被弱捕获。
Printing description of successToken:
▿ Optional<NSObject>
- some : <__NSObserver: 0x60000384e940>
Printing description of failureToken:
▿ Optional<NSObject>
- some : <__NSObserver: 0x60000384ea30>
还确认在 removeObserver(_:)
被调用后再次打印 NotificationCenter.default
观察者(据推测)被删除。
接下来,我离开视图控制器并确认引用代码中的 self
已被释放。
最后,我打开调试内存图并搜索内存地址,发现了这个:
最后没有retain cycle。只是观察者没有被移除,因为闭包是活的,所以捕获的 self
在其生命周期之外是活的。
如果你们认为这是一个错误,请发表评论。根据 NotificationCenter
上的文档,它很可能是...
最近我自己 运行 遇到了类似的问题。
这似乎不是错误,而是令牌的未记录特征(正如您已经注意到的那样)属于 __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)
}