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構建消失。