Notification KVO Observer Crash

昨天看在听云上的Crash记录,看到我们App中有和视频播放相关的Crash。昨天就点了几次发现从视频播放进入产品详情,并进行商品购物下单,最后从订单详情返回到购物车的时候App Crash。操作轨迹如下:

VideoViewController -> VideoDetailViewController -> ProductItemDetailViewController -> UCYWebViewController(结算页)-> PayWayViewController -> UCYWebViewController(订单详情页)-> UCYTabBarController(并选中购物车tab)-> UCYWebViewController(购物车) -> App Crash

对于导致App Crash的bug我们决定是零容忍的,严重导致用户体验。下面就详细说明下此次Fix这个Crash问题的过程。

Crash定位

首先Crash提示为EXC_BAD_ACCESS,看操作轨迹还以为我PopUtils工具类导致的,但是以前其他也有类似的返回,第一反应打断点单步调试。

+ (void)popToRootViewControllerWithSelectedIndex:(NSInteger)index {
    UITabBarController *tabVC = (UITabBarController *)[AppDelegate sharedAppDelegate].window.rootViewController;
    if (index >= 0 && index < tabVC.viewControllers.count) {
        [tabVC setSelectedIndex:index];
    }
    [PopUtils popToRootViewController];
}
+ (void)popToRootViewControllerAnimated:(BOOL)animated {
    UITabBarController *tabVC = (UITabBarController *)[AppDelegate sharedAppDelegate].window.rootViewController;
    for (UINavigationController *navVC in tabVC.viewControllers) {
        if (navVC.viewControllers.count > 1) {
            [navVC popToRootViewControllerAnimated:animated];
            [navVC setNavigationBarHidden:NO animated:NO];
        }
    }
}

发现并不是此处的问题,因为[UCYWebViewController viewDidAppear:NO]方法已经执行,购物车页面已经显示。

小技巧:之前在写光明都市菜园内存优化的文章中提到过的拦截器(囧,想针对这个拦截器写一篇文章,一直没来得及写),在拦截器中对Controller Life Cycle方法进行hook并进行Method Swizzling,在swizzling方法中做日志打印,可以很方便的查看所有Controller Life Cycle执行过程。

第一步短点调试无果,Log日志输出又没有具体的内容可以查看,对于EXC_BAD_ACCESS看来还是要要启用僵尸对象来进行调试才行,按住option键,点击Xcode的Run按钮,打开如下界面,勾选 Enable Zombie Objects。

启用僵尸对象

重新运行,按照之前的操作重新操作一遍,这次在log中便可以看到具体是什么原因到导致的问题。

Crash原因

原来问题出现在视频播放器这里,但是让人费解的是,视频详情的Controller正常dealloc,Controller中的播放器容器FMGVideoPlayView也正常dealloc,怎么就出现FMGVideoPlayView retain呢?

FMGVideoPlayView EXC_BAD_ACCESS解决

仔细查看源码,在视频播放完成或者页面返回的时候,需要对Player进行清理处理,其中Player有Notification监听,KVO的Observer监听等。猜想,是不是因为FMGVideoPlayView在dealloc后,这些监听并没有移除导致的EXC_BAD_ACCESS。

FMGVideoPlayView代码中写有removePlayer方法专门处理进行监听的移除,player的的释放等操作。

- (void)removePlayer {
    [self removeObserverFromPlayerItem:self.player.currentItem];
    [self removeNotification];
    [self removeShowTimer];

    [self.player pause];
    [self.danmakuView pauseAnimation];
    [self.player.currentItem cancelPendingSeeks];
    [self.player.currentItem.asset cancelLoading];
    [self removePlaybackTimeObserver];
    self.currentItem = nil;
    self.player = nil;
}

所以尝试在FMGVideoPlayView的dealloc方法中进行removePlayer,重新运行。问题得到解决。

原因

从上面的解决过程可以看出,在FMGVideoPlayView被dealloc的时候,其中注册的Notification和KVO均没有被正常remove掉,监听依然被注册着。

所以问题来了:

最后附上Apple文档链接NSKeyValueObserving感觉很有必要进去仔细看下。



code
这里是修改代码的地方!

这里是修改网站内容的地方,如果你想有文章想要发表,就给我发邮件吧!.