现象
最近做项目时发现了一个奇怪的问题,App 不知道什么操作就什么也不能操作了,点什么都没有反应,后来经过反复测试,发现是在首页先侧滑一下,然后点击跳转其他页面,然后就卡死了
卡顿原因分析
操作路径
阅读页生命周期分析
正常跳转
2021-10-28 10:52:07.772485+0800 WKStudent[9327:4048460] WKReaderContainer: viewWillAppear
2021-10-28 10:52:08.305771+0800 WKStudent[9327:4048460] WKReaderContainer: viewDidAppear
出现卡顿时
2021-10-28 10:52:09.620765+0800 WKStudent[9327:4048460] WKReaderContainer: viewWillAppear
生命周期对比
首页:viewWillDisappear
->
阅读页:viewDidLoad
->viewWillAppear
->
原因分析
点击跳转路由时实际已经跳转了,但是页面没有显示出来,上面的日志来看,是因为阅读页的viewDidAppear
方法没有执行,所有没有显示在 UIWindow 上,同样的,首页的生命周期也会有问题,正常情况时,跳转时viewWillDisappear
执行后立即会走viewDidDisappear
,但是如果出现卡顿,就只会走viewWillDisappear
。
iOS 系统如果是自定义了侧滑事件,那么在首页进行侧滑操作就会出现这个 bug,应该是个系统 bug。
如何解决?
方案一(在 BaseViewController 中禁用首页侧滑事件,现有方案)
1 | - (void)viewDidAppear:(BOOL)animated { |
但是为什么还是会卡顿呢?上述有什么问题
猜想
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
无效
证明
在回调中,输出gestureRecognizer
1 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { |
otherGestureRecognizer
结果如下
1 | <_UIParallaxTransitionPanGestureRecognizer: 0x108a75b70; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x108a67830>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x108a750c0>)>> |
从结果上看,在 App 中即使设置了不响应侧滑的代码,依然会出现_UINavigationInteractiveTransition
的handleNavigationTransition
的侧滑事件,因此造成了异常
尝试(反向推)
正常情况
1 | #import "BaseViewController.h" |
异常情况
以上代码是不会输出任何日志的,也就是说,interactivePopGestureRecognizer.enabled
设置为 NO 按常理来说是不会有调用。修改上述代码,复现其卡顿
1 | #import "BaseViewController.h" |
输出
1 | 2021-10-31 22:37:17.379692+0800 TestApp[12400:5058768] gestureRecognizer:<_UIParallaxTransitionPanGestureRecognizer: 0x108a75b70; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x108a67830>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x108a750c0>)>> |
在这样的情况下,如果进行 push 操作,就会导致 app 卡顿。和上面情况一致,都是因为相应了系统的handleNavigationTransition
事件
方案二(在 BaseViewController 中禁用首页侧滑事件,回调中解决)
1 | - (void)viewDidLoad { |
这样的方案是全部交由我们自己处理是否要响应侧滑事件,当然上述还是有问题,应该是只针对侧滑做上面的操作
后续
解决方案
- 找到所有对侧滑
delegate
作处理的地方,特别是首页self.navigationController.interactivePopGestureRecognizer.delegate = nil
- 在
BaseViewController
中重写写自己侧滑实现 - 如果有自己的手势处理,比如其他多手势操作需要注意侧滑情况的处理,主要是首页判断
- 三方库的检查,如果涉及到与滑动有关的库,特别是首页,比如 PagerView,我们 App 首页就是层级太多了(含有 ChildViewController 的需要特别注意),排查其他比较麻烦。如果使用三方库或者自己实现了其他的操作,需要多进行自测,以规避此 bug
卡顿/假死情况二
还有个卡顿是在 push 时同时进行侧滑操作
A 页面(首页)->B 页面,同时进行侧滑
- 页面 A 的生命周期:
viewWillDisappear
->viewDidDisappear
->viewWillAppear
- 页面 B 的生命周期:
viewWillAppear
->viewDidAppear
->viewWillDisappear
通过日志得知原因是页面 A 的viewDidAppear
和页面 B 的viewDidDisappear
没有走,所以给我们看到的是停留在第二个页面
解决方案
判断如果页面当前生命周期是否是viewDidAppear
,如果不是就不进行侧滑操作
1 | - (void)viewWillAppear:(BOOL)animated { |
还存在的问题
- 对于第一种情况导致
interactivePopGestureRecognizer.enabled=NO
失效的原因不明 - 猜想:可能的原因是因为 ChildViewController,首页层级太多,还有其他多个滑动事件,导致响应了侧滑事件