这个AVFoundation KVO模式对于视频播放器有什么问题[参考:AVPlayerLayer,AVPlayerItem,AVURLAsset]?

时间:2017-07-07 15:59:24

标签: avfoundation key-value-observing avplayeritem avplayerlayer avurlasset

我写了一个UIView子类“VideoPlayerView”来封装AVFoundation视频播放。我相信我设置了一个防弹KVO模式来处理AVPlayer,AVPlayerItems和AVURLAssets的观察,以便加载,回放和处理错误。

相反,我发现崩溃报告说这种模式是专门为防范而设置的(很少,但仍然报告)。

a) 类AVPlayerItem的实例0x170019730已取消分配,而键值观察者仍在其中注册。

b)[VideoPlayerView setPlayerItem:] 无法从AVPlayerItem中删除观察者VideoPlayerView的关键路径“status”,因为它未注册为观察者。

c)[VideoPlayerView setAsset:] 无法从AVURLAsset 0x170233780删除观察者VideoPlayerView 0x145e3bbd0,因为它未注册为观察者。 < / p>

我想了解为什么这些错误正在发生,我错过或误解了什么,以及如何使事情更加健壮。

为了解释的目的,简化了具体细节,但我相信所有相关信息都在这里。

我有一个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

我按如下方式实现自定义getter:

- (AVPlayer*)player
{
    return [(AVPlayerLayer*)self.layer player];
}

- (AVPlayerLayer *)playerLayer
{
    return (AVPlayerLayer *)self.layer;
}

我按如下方式实现自定义setter:

- (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中删除观察者VideoPlayerView的关键路径“status”,因为它未注册为观察者。

c)[VideoPlayerView setAsset:] 无法从AVURLAsset 0x170233780删除观察者VideoPlayerView 0x145e3bbd0,因为它未注册为观察者。 < / p>

在使用指向新的或传入的AVPlayerItem或AVURLAsset的指针替换属性之前,我的自定义setter尝试删除任何先前设置的AVPlayerItem或AVURLAsset的观察。

当我的类被实例化时,_playerItem和_asset为零。因此,任何先前的AVPlayerItem或AVURLAsset必须通过自定义setter设置,因此将VideoPlayerView注册为这些keypath的观察者。

在没有设置观察的情况下如何设置这些属性?

这些只是可怕的竞争条件是基于自定义设置器中方法调用的顺序吗?

我在这里缺少一些基本的东西吗?

我正在考虑使用objective-c运行时在这些对象上创建一个关联的对象属性BOOL isObserved只是为了能够在尝试删除观察者之前进行健全性检查。考虑到当前方法的问题,我觉得即使这样也不够强大。

任何见解或帮助非常感谢。谢谢你的阅读。

1 个答案:

答案 0 :(得分:1)

在与Apple工程师进行了长时间的对话之后,带走的消息似乎是在观察类的dealloc方法中取消注册KVO观察并不是一个好的模式。 Apple的KVO指南确实建议不要在init和dealloc方法中使用自定义setter或getter,但是我被告知文档的语言应该在这一点上更加强大 - 它永远不应该完成。

基本上,由于KVO实施的复杂性,它永远不能保证工作。它可以在某些情况下工作,但它永远不会得到保证,并且显示出高度的不可预测性 - 除非情况非常简单,否则几乎可以预期随机崩溃。

我与苹果公司就这种模式的通信中的一些选择摘录如下,转述为SO:

  

这里面临的挑战是人们如何与KVO互动   以及更复杂的使用模式如何改变行为。在里面   NSObject子类的简单情况就是在那里观察另一个对象   真的不是什么大问题。当情况变得更多时   事情开始崩溃并且变得更加丑陋。当你   你花了很多时间盯着那些破碎的怪异边缘的情况   在你的方法中变得更加偏执。

     

KVO在macOS上的相对年龄和历史也是其中的一部分。   与iOS相比,macOS应用通常具有更简单的子类化   模式 - 没有与iOS和iOS相同的ViewController类   他们倾向于严重依赖标准的UI类,所以它不在   对于macOS应用程序中的大多数类来说,直接继承都是不常见的   来自NSObject。

     

基本上,这里的问题是许多简单的案例工作得很好,   而复杂的案例......可能真的非常奇怪。这些   问题不是未知,但很多开发人员都有这个问题   如果它在他们的应用程序中“正常工作”意味着他们不一定是那样   可见。

     

以下是该观点的一个不错的概述:   http://khanlou.com/2013/12/kvo-considered-harmful/

总结:

理想情况下,KVO应该在相关类的生命周期中的明确定义的逻辑点处设置和取消设置,并且尽可能不依赖于dealloc。显然,在某些情况下,这是不可能的 - 观察必须在对象的整个生命周期中发生,可以在未公开的点(即由iOS管理,例如回收的集合视图单元格)中解除分配 - 并且在那些我被建议使用一个单独的包装类来处理KVO。

我研究并决定使用Kevin Ballard优秀的PMKVObserver包装类,而不是自己编写。它非常方便,线程安全,并且在观察者或观察对象死亡时自动处理注销。

https://github.com/postmates/PMKVObserver

在撰写本文时,使用PMKVObserver代替此dealloc-unregistration模式在构建中消除了所有这些异常。