我写了一个UIView子类“VideoPlayerView”来封装AVFoundation视频播放。我相信我有一个防弹KVO模式,用于处理观察AVPlayer,AVPlayerItems和AVURLAssets以加载,回放和错误处理。视频播放器的AVFoundation KVO模式有什么问题[ref:AVPlayerLayer,AVPlayerItem,AVURLAsset]?
相反,我发现崩溃被报道,这种模式是专门设立的防范(很少,但仍然报道)。
a)类AVPlayerItem的实例0x170019730被解除分配,而键值观察者仍然在其中注册。
二)[VideoPlayerView setPlayerItem:]不能从AVPlayerItem关键路径“状态”,因为它未注册为观察员删除观察者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 maintains a strong reference to its AVPlayer property。
我实现定制的吸气剂如下:
- (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时,其中的任何强大属性都会将其观察删除,然后再释放以允许其重新分配。
那么,我相信这个模式应该减轻我观察崩溃如下:
一)一个实例类的0x170019730 AVPlayerItem被释放,而键值观察家仍用它注册。
VideoPlayerView是我观察AVPlayerItem的唯一类。 VideoPlayerView在观察它时保持对AVPlayerItem的强引用。因此,AVPlayerItem无法在VideoPlayerView处于活动状态时解除分配,并且在释放AVPlayerItem之前,VideoPlayerView将在AVPlayerItem的后续释放之前停止观察AVPlayerItem。
这是怎么回事?
二)[VideoPlayerView setPlayerItem:]不能从AVPlayerItem关键路径“状态”,因为它未注册为观察员删除观察者VideoPlayerView。
C)[VideoPlayerView setAsset:]无法从AVURLAsset 0x170233780关键路径 “可玩”,因为它没有被注册为观察者除去观察者VideoPlayerView 0x145e3bbd0。
我的自定义setter正试图删除任何先前设置的AVPlayerItem或AVURLAsset的观察,然后用指向新的或传入的AVPlayerItem或AVURLAsset的指针替换该属性。
当我的类被实例化时,_playerItem和_asset都是零。因此,任何以前的AVPlayerItem或AVURLAsset都必须通过自定义设置器进行设置,因此已将VideoPlayerView注册为这些关键路径的观察者。
这些属性如何在未设置观察的情况下设置?
是基于自定义设置器方法调用的顺序对这些太可怕竞争条件?
这里有什么基本的东西吗?
我正在考虑使用objective-c运行时在这些对象上创建关联的对象属性BOOL isObserved,以便能够在尝试删除观察者之前执行完整性检查。我感觉即使这样做不够健壮,考虑到目前的方法论的问题。
任何见解或帮助非常感谢。谢谢你的阅读。
与崩溃无关,但在'setPlayerItem'中,您从新参数'playerItem'中删除通知观察者,而不是旧ivar'_playerItem'。 – Willeke
谢谢您指出@Willeke - 非常感谢。 –