[Swift]AVPlayer.currentItemをiOS8で取り扱うときの注意

....。
このご時世にこれだけ iOS 8 のこと書いてるのって僕だけじゃない?(笑)
ってことで、

AVPlayer.currentItemをKVOで監視してるとやばい...!!

です

Xcode v9.3.1

問題

webにあがってる動画を読み込んで流すということをしている時に、
動画のサイズ(width x height)が欲しくって
それを実現するのにAVPlayerItemstatus を監視することで実現しようとしたのです。

let player = AVPlayer(url: URL(string: "動画URL")!)
self.observation = player.currentItem?.observe(\.status, changeHandler: { (item, _) in
    switch item.status {
    case .readyToPlay:
        print(item.presentationSize)
    default: break
    }
})

こういう感じですね。
これでやりたかったことはできるのですけれど、
とある条件下でクラッシュします。

クラッシュする条件

以下の2つです。

  • iOS 8 端末であること
  • 動画URLが読み込めない(リンク切れなど)

どうやら、iOS 8 の場合読み込めないと、AVPlayerが内部でAVPlayerItemを解放するようなのです。
で、上記の例だとKVOでplayer.currentItemの status を監視しているわけですから...クラッシュするというわけです

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7867a000 of class AVPlayerItem was deallocated while key value observers were still registered with it.

解放するまえに status の監視を解除しろっていうことですね..。

ちなみにこういう感じで nil になるのをチェックしました

let player = AVPlayer(url: URL(string: "なにかリンク切れの動画リンク")!)
observation = player.observe(\.currentItem) { (player, changedValue) in
    print(player.currentItem == nil)
}

iOS 8 の場合は、trueとログが吐かれるのです。

対策

対策は簡単で、AVplayerItemのインスタンスもちゃんと自分でも参照を持つというとこですね。

self.playerItem = AVPlayerItem(url: URL(string: "なにかリンク切れのリンク")!)
let player = AVPlayer(playerItem: self.playerItem)

こういう感じで。
これで、AVPlayerが読み込めなかった時に破棄しようとしても、自分が参照もってるから防げるというわけです。
(もちろん適宜KVOは解除ひつようですよ!)

いやーー自分で持つのめんどくせ...Playerは持ってるからそいつでみたらいいでしょ!
って怠けちゃったばっかりにこういうことになりますよっと....
(どこのサンプルでも大抵はAVPlayerItemをちゃんと持つようになってるから、多くの人は大丈夫なはず....😇)

変な方向で怠けるとだいたい後ろから襲われますね...