класс AVPlayerItem был освобожден, в то время как наблюдатели значения ключа все еще были зарегистрированы

Я получаю следующую ошибку, но странно то, что это происходит только тогда, когда я делаю прокрутку в своем табличном представлении с помощью разбиения на страницы, загружаю второй набор сообщений, а затем пытаюсь перейти к новому контроллеру представления, если я загрузить только первую страницу сообщений и перейти к другому контроллеру представления, сбоя не будет:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'An instance 0x170212330 of class AVPlayerItem was deallocated while key 
value observers were still registered with it. 
Current observation info: <NSKeyValueObservationInfo 0x174820360> (
<NSKeyValueObservance 0x174244290: Observer: 0x101c76c00, Key path: 
playbackBufferEmpty, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, 
Property: 0x174243ea0>
<NSKeyValueObservance 0x1754583c0: Observer: 0x101c76c00, Key path: 
playbackLikelyToKeepUp, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, 
Property: 0x174243f60>

У меня есть плеер на моем мобильном телефоне, вот мой код для контроллера просмотра:

- (UITableViewCell *)tableView:(UITableView *)tableViewSelected cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *identifier = @"postCell";
    PostCell* updateCell = [tableViewSelected dequeueReusableCellWithIdentifier:identifier];

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

        dispatch_async(queue, ^{

            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"ADD VIDEO PLAYER and PLAY VIDEO");



                NSString* videoString = [NSString stringWithFormat:@"%@%@%@",UrlBase,PostVideos,post.video];
                NSString* expandedPath = [videoString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
                 NSURL *videoURL = [NSURL URLWithString:expandedPath];
                NSLog(@"URL : %@",videoURL);


                updateCell.videoItem  = [AVPlayerItem playerItemWithURL:videoURL];

                [updateCell.videoItem addObserver:updateCell forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
                [updateCell.videoItem addObserver:updateCell forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];

                updateCell.videoPlayer = [AVPlayer playerWithPlayerItem:updateCell.videoItem];
                updateCell.avLayer = [AVPlayerLayer playerLayerWithPlayer:updateCell.videoPlayer];
                updateCell.videoPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;

                [[NSNotificationCenter defaultCenter] addObserver:updateCell selector:@selector(itemDidBufferPlaying:) name:AVPlayerItemPlaybackStalledNotification object:nil];
                [[NSNotificationCenter defaultCenter] addObserver:updateCell selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:[updateCell.videoPlayer currentItem]];


                updateCell.avLayer.frame = updateCell.picture.bounds;
                [updateCell.videoView.layer addSublayer:updateCell.avLayer];

                [updateCell.avLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
                if(indexPath.row==0){
                    [updateCell.videoPlayer play];
                }
            });
        });
        return updateCell;

}

И единственное отличие, которое я вижу от добавления второй страницы, это:

При обычной загрузке табличного представления я делаю:

  [_tableView reloadData];

При загрузке второй страницы в том же представлении таблицы я делаю:

 [_tableView beginUpdates];
 [_tableView insertRowsAtIndexPaths:[[GlobalSingleton sharedInstance] indexPathsToInsert]  withRowAnimation:UITableViewRowAnimationTop];
 [_tableView endUpdates];

Чем на моем Cell я делаю:

- (void)prepareForReuse{
    [self removePlayer];

}

- (void) dealloc {
[self removePlayer];
}

-(void)removePlayer{
    @try{

    [self.videoItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
    [self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];




    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

        NSLog(@"remove Observer!");
    // [avLayer.player pause];
    }
    @catch (NSException * e) {
        NSLog(@"Exception: %@", e);
    }
    @finally {
        NSLog(@"finally");
        [self.avLayer removeFromSuperlayer];
        self.playIV.hidden = YES;
        self.videoActivity.hidden = YES;
        self.videoView.hidden = YES;
        self.videoItem = nil;
        self.avLayer = nil;
        self.videoPlayer = nil;
    }

}

Возможно ли, что insertRowsAtIndexPaths приводит к тому, что ячейки никогда не освобождаются? Я не могу найти, как можно зарегистрировать наблюдателей.


person Gil Beyruth    schedule 07.03.2017    source источник
comment
Мое решение состояло в том, чтобы удалить двух первых наблюдателей: [updateCell.videoItem addObserver:updateCell forKeyPath:@playbackBufferEmpty options:NSKeyValueObservingOptionNew context:nil];[updateCell.videoItem addObserver:updateCell forKeyPath:@playbackLikelyToKeepUp options:NSKeyValueObservingOptionNew context:nil];   -  person Gil Beyruth    schedule 07.03.2017
comment
Также я понял, что prepareForReuse не очень хорошо работает с insertRowsAtIndexPaths, но если я удалю наблюдателей на cellForRowAtIndexPath перед созданием нового videoItem, это также сработает.   -  person Gil Beyruth    schedule 07.03.2017


Ответы (1)


Каким-то образом при использовании insertRowsAtIndexPaths prepareForReuse не вызывается, поэтому я разрешил это, отменив регистрацию наблюдателей в cellForRowAtIndexPath перед добавлением нового видеоэлемента в ячейку:

- (UITableViewCell *)tableView:(UITableView *)tableViewSelected cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = @"postCell";
PostCell* updateCell = [tableViewSelected dequeueReusableCellWithIdentifier:identifier];

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

    dispatch_async(queue, ^{

        dispatch_sync(dispatch_get_main_queue(), ^{
            NSString* videoString = [NSString stringWithFormat:@"%@%@%@",UrlBase,PostVideos,post.video];
            NSString* expandedPath = [videoString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
             NSURL *videoURL = [NSURL URLWithString:expandedPath];

            /** SOLUTION STARTS HERE **/
            @try{
                  [self.videoItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
                  [self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
                  [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil];
                  [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

           }
           @catch (NSException * e) {
               NSLog(@"Exception: %@", e);
           }
           @finally {
               NSLog(@"finally");
           }
           /** SOLUTION ENDS HERE **/

            updateCell.videoItem  = [AVPlayerItem playerItemWithURL:videoURL];
            [updateCell.videoItem addObserver:updateCell forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
            [updateCell.videoItem addObserver:updateCell forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];

            updateCell.videoPlayer = [AVPlayer playerWithPlayerItem:updateCell.videoItem];
            updateCell.avLayer = [AVPlayerLayer playerLayerWithPlayer:updateCell.videoPlayer];
            updateCell.videoPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;

            [[NSNotificationCenter defaultCenter] addObserver:updateCell selector:@selector(itemDidBufferPlaying:) name:AVPlayerItemPlaybackStalledNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:updateCell selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:[updateCell.videoPlayer currentItem]];


            updateCell.avLayer.frame = updateCell.picture.bounds;
            [updateCell.videoView.layer addSublayer:updateCell.avLayer];

            [updateCell.avLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
            if(indexPath.row==0){
                [updateCell.videoPlayer play];
            }
        });
    });
    return updateCell;

}
person Gil Beyruth    schedule 08.03.2017