iOS侧滑假死卡顿问题分析

示例

现象

最近做项目时发现了一个奇怪的问题,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
2
3
4
5
6
7
8
9
10
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self.navigationController.viewControllers count] > 1) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
} else {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}

但是为什么还是会卡顿呢?上述有什么问题

猜想

self.navigationController.interactivePopGestureRecognizer.enabled = NO; 无效

证明

在回调中,输出gestureRecognizer

1
2
3
4
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
NSLog(@"gestureRecognizer:%@", gestureRecognizer);
return YES;
}

otherGestureRecognizer结果如下

1
<_UIParallaxTransitionPanGestureRecognizer: 0x108a75b70; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x108a67830>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x108a750c0>)>>

从结果上看,在 App 中即使设置了不响应侧滑的代码,依然会出现_UINavigationInteractiveTransitionhandleNavigationTransition的侧滑事件,因此造成了异常

尝试(反向推)

正常情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "BaseViewController.h"

@interface BaseViewController ()<UIGestureRecognizerDelegate>
@end

@implementation BaseViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.interactivePopGestureRecognizer.delegate = self;
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
NSLog(@"gestureRecognizer:%@", gestureRecognizer);
return YES;
}

@end
异常情况

以上代码是不会输出任何日志的,也就是说,interactivePopGestureRecognizer.enabled设置为 NO 按常理来说是不会有调用。修改上述代码,复现其卡顿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "BaseViewController.h"

@interface BaseViewController ()<UIGestureRecognizerDelegate>
@end

@implementation BaseViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.interactivePopGestureRecognizer.delegate = self;
self.navigationController.interactivePopGestureRecognizer.enabled = YES; // 修改为可以接收响应事件
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
NSLog(@"gestureRecognizer:%@", gestureRecognizer);
return YES;
}
@end

输出

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
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)viewDidLoad {
[super viewDidLoad];
// ...
self.navigationController.interactivePopGestureRecognizer.delegate = self;
// ...
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([self.navigationController.viewControllers count] > 1) {
return YES;
} else {
return NO;
}
}

这样的方案是全部交由我们自己处理是否要响应侧滑事件,当然上述还是有问题,应该是只针对侧滑做上面的操作

后续

解决方案

  • 找到所有对侧滑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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.isViewDidAppear = NO;
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.isViewDidAppear = YES;
}

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.isViewDidAppear = NO;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// 防止还没有push完成就进行侧滑操作,这会导致假死bug
if (self.isViewDidAppear) {
return [self.navigationController.viewControllers count] > 1;
}
return NO;
}

还存在的问题

  • 对于第一种情况导致interactivePopGestureRecognizer.enabled=NO 失效的原因不明
  • 猜想:可能的原因是因为 ChildViewController,首页层级太多,还有其他多个滑动事件,导致响应了侧滑事件
坚持原创技术分享,您的支持将鼓励我继续创作!