Я написал подкласс UIView «VideoPlayerView», чтобы инкапсулировать воспроизведение видео AVFoundation. Я полагал, что у меня есть пуленепробиваемый шаблон KVO, настроенный для наблюдения за AVPlayer, AVPlayerItems и AVURLAssets с целью загрузки, воспроизведения и обработки ошибок.
Вместо этого я нахожу сообщения о сбоях, от которых этот шаблон был специально настроен для защиты (редко, но, тем не менее, о них сообщают).
a) Экземпляр 0x170019730 класса AVPlayerItem был освобожден, хотя для него все еще были зарегистрированы наблюдатели значений ключа.
b) [VideoPlayerView setPlayerItem:] Невозможно удалить наблюдатель VideoPlayerView для ключевого пути "status" из AVPlayerItem, поскольку он не зарегистрирован в качестве наблюдателя.
c) [VideoPlayerView setAsset:] Невозможно удалить наблюдатель VideoPlayerView 0x145e3bbd0 для ключевого пути, "воспроизводимого" из AVURLAsset 0x170233780, так как он не зарегистрирован в качестве наблюдателя.
Я хотел бы узнать, почему возникают эти ошибки, что я пропустил или неправильно понял и как сделать все более надежным.
Конкретные детали упрощены для целей объяснения, но я считаю, что вся необходимая информация находится здесь.
У меня есть класс 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 реализует метод расщепления следующим образом:
- (void) dealloc
{
[self releasePlayerAndAssets];
}
- (void) releasePlayerAndAssets
{
[self setAsset:nil];
[self setPlayerItem:nil];
[self setPlayer:nil];
}
Да, я должен просто встроить эту бессмысленную абстракцию! Тем не менее, это означает, что при освобождении VideoPlayerView любые сильные свойства в нем удаляются, а затем освобождаются, чтобы разрешить их освобождение.
Итак, я считаю, что этот шаблон должен смягчить сбои, которые я наблюдаю, следующим образом:
a) Экземпляр 0x170019730 класса AVPlayerItem был освобожден, хотя для него все еще были зарегистрированы наблюдатели значений ключа.
VideoPlayerView — единственный мой класс, наблюдающий за AVPlayerItem. VideoPlayerView поддерживает сильную ссылку на AVPlayerItem все время, пока наблюдает за ним. Поэтому AVPlayerItem не может быть освобожден, пока VideoPlayerView активен, и до его освобождения VideoPlayerView перестанет наблюдать за AVPlayerItem до последующего освобождения AVPlayerItem.
Как это происходит не так?
b) [VideoPlayerView setPlayerItem:] Невозможно удалить наблюдатель VideoPlayerView для ключевого пути "status" из AVPlayerItem, поскольку он не зарегистрирован в качестве наблюдателя.
c) [VideoPlayerView setAsset:] Невозможно удалить наблюдатель VideoPlayerView 0x145e3bbd0 для ключевого пути, "воспроизводимого" из AVURLAsset 0x170233780, так как он не зарегистрирован в качестве наблюдателя.
Мои пользовательские установщики пытаются удалить наблюдение за любым ранее установленным AVPlayerItem или AVURLAsset до замены свойства указателем на новый или входящий AVPlayerItem или AVURLAsset.
Когда создается экземпляр моего класса, _playerItem и _asset равны нулю. Поэтому любой предыдущий AVPlayerItem или AVURLAsset должен быть установлен с помощью пользовательского установщика, и, следовательно, VideoPlayerView должен быть зарегистрирован как наблюдатель для этих путей.
Как задаются эти свойства без настройки наблюдения?
Это просто ужасные условия гонки, основанные на порядке вызовов методов в пользовательских сеттерах?
Есть ли что-то фундаментальное, что мне здесь не хватает?
Я рассматриваю возможность использования среды выполнения target-c для создания связанного свойства объекта BOOL isObserved для этих объектов, чтобы иметь возможность выполнить проверку работоспособности перед попыткой удалить наблюдателя. Я чувствую, что даже это не будет достаточно надежным, учитывая проблемы с текущей методологией.
Любое понимание или помощь высоко ценятся. Спасибо за чтение.
setPlayerItem
вы удаляете наблюдателей уведомлений из нового параметраplayerItem
вместо старого ивара_playerItem
. - person Willeke   schedule 07.07.2017