NSKeyValueObservation: не удается удалить наблюдателя для ключевого пути из объекта, поскольку он не зарегистрирован в качестве наблюдателя

Я получаю случайные сбои (которые я не могу воспроизвести на устройствах, которыми я владею) в своем приложении с исключением:

Не удается удалить наблюдателя Foundation.NSKeyValueObservation 0xaddress для ключевого пути "readyForDisplay" из файла AVPlayerLayer 0xaddress, потому что он не зарегистрирован в качестве наблюдателя.

Это происходит, когда я освобождаю UIView, который содержит AVPlayerLayer.

Мой 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 {
    self.kvoPlayerObservers.forEach { $0.invalidate() }
    ...
    NotificationCenter.default.removeObserver(self)
}

Согласно Crashlytics, это происходит на iOS 11.4.1 на разных iPhone.

Код, приводящий к deinit, довольно прост:

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

Я был бы признателен за любые мысли о том, почему это происходит.

Я видел эту ошибку, но, похоже, это не причина для меня.

ИЗМЕНИТЬ 1:

Дополнительная информация для потомков. На iOS 10, если я не сделаю недействительным, я получаю воспроизводимый крах на deinit. На iOS 11 он работает без аннулирования (еще не проверен, если сбой исчезнет, если я не сделаю недействительным и пусть наблюдатели будут deinit с моим классом).

EDIT 2:

Дополнительная информация для потомков: Я также нашел эту ошибку Swift, которая может быть связана с SR-6795.

Ответы

Ответ 1

После

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

добавлять

self.kvoPlayerObservers.removeAll()

Также мне не нравится эта строка:

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

kvoPlayerObservers должны быть Set, и вы должны вставлять наблюдателей один за другим, когда вы их получите.

Ответ 2

Я принял матовый ответ, но я хочу предоставить дополнительную информацию о том, как я действительно решал свою проблему.

Мой деинит, который не падает, выглядит следующим образом:

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 недели, и с тех пор я не получал ни аварий, ни журналов исключений, поэтому я предполагаю, что матовое предложение добавить kvoPlayerObservers.removeAll() было правильным (по крайней мере для моего конкретного случая).