AVFoundation のビデオ再生をカプセル化するために、UIView サブクラス「VideoPlayerView」を作成しました。AVPlayer、AVPlayerItems、および AVURLAssets の読み込み、再生、およびエラー処理の監視を処理するために、防弾 KVO パターンをセットアップしたと思います。
代わりに、このパターンが防御するために特別に設定されたというクラッシュが報告されているのを見つけました (めったにありませんが、それでも報告されています)。
a) クラス AVPlayerItem のインスタンス 0x170019730 の割り当てが解除されましたが、キー値オブザーバーがまだ登録されていました。
b) [VideoPlayerView setPlayerItem:] オブザーバーとして登録されていないため、AVPlayerItem からキー パス "status" のオブザーバー VideoPlayerView を削除できません。
c) [VideoPlayerView setAsset:] オブザーバーとして登録されていないため、AVURLAsset 0x170233780 からキー パス「再生可能」のオブザーバー VideoPlayerView 0x145e3bbd0 を削除できません。
なぜこれらのエラーが発生するのか、何を見逃していたのか、何を誤解していたのか、そして物事をより堅牢にする方法を学びたいと思います。
説明のために特定の詳細は簡略化されていますが、関連する情報はすべてここにあると思います。
クラス VideoPlayerView があり、これらのプロパティをとりわけ保持しています。
@property (strong, nonatomic) AVPlayerItem *playerItem;
@property (strong, nonatomic) AVURLAsset *asset;
@property (strong, nonatomic, readonly) AVPlayerLayer *playerLayer;
すべての参照は強力であることに注意してください。これらのオブジェクトは、VideoPlayerView (監視を行っている) 自体の割り当てが解除されるまで、割り当てを解除できません。 AVPlayerLayer は、その AVPlayer プロパティへの強力な参照を維持します。
次のようにカスタムゲッターを実装します。
- (AVPlayer*)player
{
return [(AVPlayerLayer*)self.layer player];
}
- (AVPlayerLayer *)playerLayer
{
return (AVPlayerLayer *)self.layer;
}
次のようにカスタム セッターを実装します。
- (void) setPlayer:(AVPlayer*)player
{
// Remove observation for any existing player
AVPlayer *oldPlayer = [self player];
[oldPlayer removeObserver:self forKeyPath:kStatus];
[oldPlayer removeObserver:self forKeyPath:kCurrentItem];
// Set strong player reference
[(AVPlayerLayer*)[self layer] setPlayer:player];
// Add observation for new player
[player addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionNew context:kVideoPlayerViewKVOContext];
[player addObserver:self forKeyPath:kCurrentItem options:NSKeyValueObservingOptionNew context:kVideoPlayerViewKVOContext];
}
- (void) setAsset:(AVURLAsset *)asset
{
// Remove observation for any existing asset
[_asset removeObserver:self forKeyPath:kPlayable];
// Set strong asset reference
_asset = asset;
// Add observation for new asset
[_asset addObserver:self forKeyPath:kPlayable options:NSKeyValueObservingOptionNew context:kVideoPlayerViewKVOContext];
}
- (void) setPlayerItem:(AVPlayerItem *)playerItem
{
// Remove observation for any existing item
[_playerItem removeObserver:self forKeyPath:kStatus];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
[nc removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:_playerItem];
[nc removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:_playerItem];
// Set strong playerItem reference
_playerItem = playerItem;
// Add observation for new item
[_playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionNew context:kVideoPlayerViewKVOContext];
if (_playerItem)
{
[nc addObserver:self selector:@selector(handlePlayerItemDidReachEndTimeNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
[nc addObserver:self selector:@selector(handlePlayerItemFailureNotification:) name:AVPlayerItemPlaybackStalledNotification object:_playerItem];
[nc addObserver:self selector:@selector(handlePlayerItemFailureNotification:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_playerItem];
}
}
これらのカスタム セッター以外では、VideoPlayerView は常に "self.property =" または "[self setProperty:]" を使用し、"_property =" を使用しないため、カスタム セッターが常に使用されます。
最後に、VideoPlayerView は次のように dealloc メソッドを実装します。
- (void) dealloc
{
[self releasePlayerAndAssets];
}
- (void) releasePlayerAndAssets
{
[self setAsset:nil];
[self setPlayerItem:nil];
[self setPlayer:nil];
}
はい、この無意味な抽象化をインライン化する必要があります! それにもかかわらず、これは、VideoPlayerView の割り当てが解除されると、そこに含まれるすべての強力なプロパティが監視を削除され、解放されて割り当てが解除されることを意味します。
したがって、このパターンは、私が観察しているクラッシュを次のように軽減するはずです。
a) クラス AVPlayerItem のインスタンス 0x170019730 の割り当てが解除されましたが、キー値オブザーバーがまだ登録されていました。
VideoPlayerView は、AVPlayerItem を観察する私の唯一のクラスです。VideoPlayerView は、監視している間ずっと AVPlayerItem への強力な参照を維持します。したがって、VideoPlayerView が動作している間は AVPlayerItem の割り当てを解除することはできず、その割り当てを解除する前に、VideoPlayerView は AVPlayerItem の後続の割り当て解除の前に AVPlayerItem の監視を停止します。
これはどのようにうまくいかないのですか?
b) [VideoPlayerView setPlayerItem:] オブザーバーとして登録されていないため、AVPlayerItem からキー パス "status" のオブザーバー VideoPlayerView を削除できません。
c) [VideoPlayerView setAsset:] オブザーバーとして登録されていないため、AVURLAsset 0x170233780 からキー パス「再生可能」のオブザーバー VideoPlayerView 0x145e3bbd0 を削除できません。
私のカスタム セッターは、プロパティを新規または着信 AVPlayerItem または AVURLAsset へのポインターに置き換える前に、以前に設定された AVPlayerItem または AVURLAsset の監視を削除しようとしています。
私のクラスがインスタンス化されるとき、_playerItem と _asset は nil です。したがって、以前の AVPlayerItem または AVURLAsset はすべて、カスタム セッターを介して設定されている必要があり、VideoPlayerView がこれらのキーパスのオブザーバーとして登録されている必要があります。
これらのプロパティは、監視を設定せずにどのように設定されていますか?
これらは、カスタム セッターでのメソッド呼び出しの順序に基づく恐ろしい競合状態ですか?
ここで私が見逃している基本的なものはありますか?
object-c ランタイムを使用して、これらのオブジェクトに関連付けられたオブジェクト プロパティ BOOL isObserved を作成し、オブザーバーを削除する前にサニティ チェックを実行できるようにすることを検討しています。現在の方法論の問題を考えると、これでも十分に堅牢ではない気がします。
どんな洞察や助けも大歓迎です。読んでくれてありがとう。