1

我写了一个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,以便能够在尝试删除观察者之前执行完整性检查。我感觉即使这样做不够健壮,考虑到目前的方法论的问题。

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

+0

与崩溃无关,但在'setPlayerItem'中,您从新参数'playerItem'中删除通知观察者,而不是旧ivar'_playerItem'。 – Willeke

+0

谢谢您指出@Willeke - 非常感谢。 –

回答

1

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

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

一些选择摘录从我的信件与苹果关于这种模式可循,转述了这么:

这里的挑战是人们如何与志愿 以及如何更复杂的使用模式游移行为互动的广阔跨度。在观察另一个对象的NSObject子类的简单情况下, 确实不是什么大问题。当情况变得更加复杂时,情况开始崩溃,并且变得更加丑陋。当你花费大量时间盯着破裂的奇怪边缘情况时,你的方法会让你更加偏执狂。

KVO在macOS上的相对年龄和历史也是其中的一部分。 与iOS相比,macOS应用程序通常具有更简单的子类化 模式 - 没有像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

在写这篇文章的时候,所有这些异常都在这个地方的dealloc,注销模式的使用PMKVObserver构建消失。