iOS UIPageViewController系统方法崩溃修复

一切始于这个神奇的报错

Last Exception Backtrace:
0   CoreFoundation                	0x1a3692a48 __exceptionPreprocess + 220 (NSException.m:199)
1   libobjc.A.dylib               	0x1a33b9fa4 objc_exception_throw + 56 (objc-exception.mm:565)
2   CoreFoundation                	0x1a35881c0 +[NSException raise:format:] + 108 (NSException.m:155)
3   UIKitCore                     	0x1a7078bbc -[UIPageViewController _validatedViewControllersForTransitionWithViewControllers:animated:] + 512 (UIPageViewController.m:1223)
4   UIKitCore                     	0x1a70796e0 -[UIPageViewController _setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:] + 560 (UIPageViewController.m:1397)
5   UIKitCore                     	0x1a707c4d8 -[UIPageViewController _handlePanGesture:] + 292 (UIPageViewController.m:0)
6   UIKitCore                     	0x1a72f3630 -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 48 (UIGestureRecognizer.m:129)
7   UIKitCore                     	0x1a72fbd10 _UIGestureRecognizerSendTargetActions + 124 (UIGestureRecognizer.m:1282)
8   UIKitCore                     	0x1a72f94a4 _UIGestureRecognizerSendActions + 352 (UIGestureRecognizer.m:1321)
9   UIKitCore                     	0x1a72f8a04 -[UIGestureRecognizer _updateGestureForActiveEvents] + 652 (UIGestureRecognizer.m:0)
10  UIKitCore                     	0x1a72ec998 _UIGestureEnvironmentUpdate + 2108 (UIGestureEnvironment.m:160)
11  UIKitCore                     	0x1a72ec110 -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 372 (UIGestureEnvironment.m:1143)
12  UIKitCore                     	0x1a72ebed4 -[UIGestureEnvironment _updateForEvent:window:] + 204 (UIGestureEnvironment.m:1112)
13  UIKitCore                     	0x1a77416f8 -[UIWindow sendEvent:] + 3324 (UIWindow.m:2876)
14  UIKitCore                     	0x1a771de2c -[UIApplication sendEvent:] + 344 (UIApplication.m:11171)

作为系统控件而言, UIPageViewController着实不让人省心, 坑坑洼洼就罢, 还能通过外部曲线救国, 这次直接在内部方法崩溃了, 一时半会还没有复现的方法

当然了, 要是没解决此问题的话, 也不会有这篇文章

节约时间, 先上代码

@implementation UIPageViewController (fix)
+ (void)fix {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method dstMethod = class_getInstanceMethod([UIPageViewController class], NSSelectorFromString(@"_setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:"));
        Method srcMethod = class_getInstanceMethod(self, @selector(fix_setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:));
        method_exchangeImplementations(dstMethod, srcMethod);
    });
}

- (void)fix_setViewControllers:(nullable NSArray<UIViewController *> *)viewControllers
                  withCurlOfType:(UIPageViewControllerTransitionStyle)type
                    fromLocation:(CGPoint)location
                       direction:(UIPageViewControllerNavigationDirection)direction
                        animated:(BOOL)animated
                  notifyDelegate:(BOOL)notifyDelegate
                      completion:(void (^ __nullable)(BOOL finished))completion {
    if (!viewControllers.count) return;
    
    [self fix_setViewControllers:viewControllers withCurlOfType:type fromLocation:location direction:direction animated:animated notifyDelegate:notifyDelegate completion:completion];
}
@end

复现

要不是没地方copy代码, 我怎么会考虑复现自己修bug呢?

使用UIPageViewController的属性设置是UIPageViewControllerTransitionStylePageCurlUIPageViewControllerNavigationOrientationHorizontal

已知这个问题是在翻页中出现的, 但等待翻页触发崩溃过于漫长, 太过费时, 甚至考虑准备弄个自动化测试

毕竟我懒, 既然是翻页中出现的问题, 那么先排查翻页的过程

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(nonnull UIViewController *)viewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(nonnull UIViewController *)viewController

页面前后翻时的Delegate, 添加代码, 直接输出控制台

若干次尝试后...

出现了一个诡异的事, 在我乱翻之下, 短时间内可能触发2次向前翻页

再次尝试若干次...

期望的行为是左右滑翻页, 但上下滑动也可能瞬间触发2次向前翻页且界面没有发生可见的变化, 触发2次前翻时大概率发生崩溃

搞事

根据苹果爸爸的崩溃日志, 最终方法发生在这里

[UIPageViewController _validatedViewControllersForTransitionWithViewControllers:animated:]

原因是空数组, 那就明显是ViewControllers:的参数出了问题

既然都复现了, 也找到了方法, 剩下的就是复刻基本套路了

for (hook系统方法; !搞定; hook上一层) {
    直接return || 换参数
}

最后在上一层[UIPageViewController _setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:]这里直接return异常清空就行了

后记

理论上比较完备的测试是需要考虑Swift的情况, 想了想...还是留给勤快的有缘人好了

posted @ 2020-06-03 00:55  Simon_X  阅读(591)  评论(0编辑  收藏  举报