NSKeyValueObservation:无法从对象中删除键路径的观察者,因为它没有注册为观察者

时间:2018-09-24 12:04:41

标签: ios swift key-value-observing avplayerlayer

我的应用出现随机崩溃(无法在我拥有的设备上重现),但异常:

  

由于未注册为观察者,因此无法从AVPlayerLayer 0xaddress中删除键路径“ readyForDisplay”的观察者Foundation.NSKeyValueObservation 0x地址。

当我取消分配包含AVPlayerLayer的UIView时会发生这种情况。

我的初始化:

private var playerLayer : AVPlayerLayer { return self.layer as! AVPlayerLayer }

init(withURL url : URL) {
    ...
    self.asset = AVURLAsset(url: url)
    self.playerItem = AVPlayerItem(asset: self.asset)
    self.avPlayer = AVPlayer(playerItem: self.playerItem)
    super.init(frame: .zero)
    ...
    let avPlayerLayerIsReadyForDisplayObs = self.playerLayer.observe(\AVPlayerLayer.isReadyForDisplay, options: [.new]) { [weak self] (plLayer, change) in ... }
    self.kvoPlayerObservers = [..., avPlayerLayerIsReadyForDisplayObs, ...]
    ...
    }

我的引发异常的deinit:

deinit {
    self.kvoPlayerObservers.forEach { $0.invalidate() }
    ...
    NotificationCenter.default.removeObserver(self)
}

根据Crashlytics,它发生在不同iPhone上的iOS 11.4.1上。

导致deinit的代码非常简单:

// Some UIViewController context.
self.viewWithAVLayer?.removeFromSuperview()
self.viewWithAVLayer = nil

我很想知道为什么会这样。

我见过this bug,但这似乎不是我的原因。

编辑1:

后代的其他信息。在iOS 10上,如果我不无效,则在deinit上会出现可重现的崩溃。在iOS 11上,它可以在没有失效的情况下正常工作(如果我不失效并让观察者与我的班级一起deinit,崩溃是否会消失还没有检查)。

编辑2:

后代的其他信息:我还发现了这个可能与Swift相关的错误-SR-6795

2 个答案:

答案 0 :(得分:8)

之后

self.kvoPlayerObservers.forEach { $0.invalidate() }

添加

self.kvoPlayerObservers.removeAll()

我也不喜欢这一行:

self.kvoPlayerObservers = [..., avPlayerLayerIsReadyForDisplayObs, ...]

kvoPlayerObservers应该是一个Set,接收观察者时,应一个接一个地插入它们。

答案 1 :(得分:0)

我已经接受了 matt 的回答,但是我想提供更多有关如何实际解决问题的信息。

我不会崩溃的deinit看起来像这样:

if let exception = tryBlock({ // tryBlock is Obj-C exception catcher.
        self.kvoPlayerObservers.forEach { $0.invalidate() };
        self.kvoPlayerObservers.removeAll()
}) {
    remoteLoggingSolution.write(exception.description)
}
... // do other unrelated stuff

基本上,我尝试捕获Obj-C异常(如果发生),并尝试将其远程记录。

过去2周,我已经将此代码投入生产,从那时起,我既没有收到崩溃也没有收到异常日志,因此我认为 matt 的添加kvoPlayerObservers.removeAll()的建议是正确的(至少对于我的特定情况而言。)