web-dev-qa-db-ja.com

NSKeyValueObservation:オブジェクトからオブザーバーとして登録されていないため、オブジェクトからキーパスのオブザーバーを削除できません

アプリでランダムなクラッシュ(所有しているデバイスでは再現できません)が発生しますが、例外があります。

オブザーバーとして登録されていないため、AVPlayerLayer 0xaddressからキーパス「readyForDisplay」のオブザーバーFoundation.NSKeyValueObservation 0xaddressを削除できません。

これは、AVPlayerLayerを含むUIViewの割り当てを解除すると発生します。

私のinit:

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

これがなぜ起こるかについての私はどんな考えにも感謝します。

私は このバグ を見ましたが、それは私にとって原因ではないようです。

編集1:

後世のための追加情報。 iOS 10では、無効化しないと、deinitで再現可能なクラッシュが発生します。 iOS 11では無効化なしで動作します(無効化せずにオブザーバーにクラスをdeinitedさせるとクラッシュが消えてもまだチェックされません)。

編集2:

後世のための追加情報:私はこれも発見しましたSwift関連する可能性のあるバグ- SR-6795

14
iur

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

追加

self.kvoPlayerObservers.removeAll()

また、この行は好きではありません。

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

kvoPlayerObserversはセットであり、オブザーバーを受け取ったら1つずつ挿入する必要があります。

9
matt

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週間運用していて、それ以来、クラッシュも例外ログも受信しなかったので、mattkvoPlayerObservers.removeAll()を追加するという提案は正しい(少なくとも私の特定のケースでは)。

0
iur