iOS 12 导航栈崩溃

这个崩溃在 iOS 12 上出现的 Can't add self as subview 问题与 iOS 的导航控制器转场动画机制有关,特别是在 iOS 12 上的一些已知问题。以下是详细分析和针对性解决方案:

iOS 12 特有原因分析

iOS 12 导航转场特性

  • 问题版本:iOS 12 的 _UINavigationParallaxTransition 动画实现有缺陷
  • 触发条件:快速连续导航操作、视图控制器生命周期混乱
  • 系统版本特定:iOS 12 的 UIKit 实现与其他版本不同

堆栈关键点分析

4 UIKitCore ___53-[_UINavigationParallaxTransition animateTransition:]_block_invoke_2 + 1452
  • 1452偏移量:表明在转场动画的某个特定代码块中
  • iOS 12差异:与之前看到的1716偏移量不同,说明代码实现有变化

iOS 12 针对性解决方案

方案一:iOS 12 特定检测和规避

// iOS 12 专用导航保护
@interface iOS12NavigationProtector : NSObject
@property (nonatomic, assign) BOOL isiOS12;
@end

@implementation iOS12NavigationProtector

+ (instancetype)shared {
    static iOS12NavigationProtector *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[iOS12NavigationProtector alloc] init];
        instance.isiOS12 = [[[UIDevice currentDevice] systemVersion] containsString:@"12"];
    });
    return instance;
}

- (BOOL)shouldProtectNavigation {
    return self.isiOS12;
}

- (void)safePerformNavigationTransition:(UINavigationController *)navController 
                               operation:(void(^)(void))operation {
    if (![self shouldProtectNavigation]) {
        if (operation) operation();
        return;
    }
    
    // iOS 12 特殊保护逻辑
    [self protectiOS12Navigation:navController operation:operation];
}

- (void)protectiOS12Navigation:(UINavigationController *)navController operation:(void(^)(void))operation {
    // 检查导航控制器状态
    if (navController.topViewController.view.window == nil) {
        NSLog(@"⚠️ iOS12: 视图未附加到窗口,延迟操作");
        [self performSelector:@selector(delayedNavigationOperation:) withObject:operation afterDelay:0.05];
        return;
    }
    
    // 检查转场是否正在进行
    if ([self isNavigationTransitioning:navController]) {
        NSLog(@"⚠️ iOS12: 转场进行中,排队操作");
        [self queueNavigationOperation:operation];
        return;
    }
    
    // 检查视图层次一致性
    if (![self validateViewHierarchy:navController]) {
        NSLog(@"⚠️ iOS12: 视图层次不一致,修复后执行");
        [self fixViewHierarchy:navController completion:^{
            if (operation) operation();
        }];
        return;
    }
    
    @try {
        if (operation) operation();
    } @catch (NSException *exception) {
        NSLog(@"❌ iOS12: 导航操作异常: %@", exception);
        [self recoverFromiOS12Crash:navController];
    }
}

- (BOOL)isNavigationTransitioning:(UINavigationController *)navController {
    // iOS 12 特定的转场状态检测
    UIView *navigationBar = navController.navigationBar;
    if (navigationBar.layer.animationKeys.count > 0) {
        return YES;
    }
    
    // 检查交互式转场
    if (navController.interactivePopGestureRecognizer.state == UIGestureRecognizerStateBegan ||
        navController.interactivePopGestureRecognizer.state == UIGestureRecognizerStateChanged) {
        return YES;
    }
    
    return NO;
}

- (BOOL)validateViewHierarchy:(UINavigationController *)navController {
    // iOS 12 视图层次验证
    for (UIViewController *vc in navController.viewControllers) {
        if (vc.view.superview == vc.view) {
            // 检测到自添加
            return NO;
        }
        
        if (vc.view.window && vc.view.superview != vc.view.window) {
            // 视图层次不一致
            return NO;
        }
    }
    return YES;
}

- (void)fixViewHierarchy:(UINavigationController *)navController completion:(void(^)(void))completion {
    // iOS 12 视图层次修复
    UIViewController *topVC = navController.topViewController;
    
    if (topVC.view.superview == topVC.view) {
        // 修复自添加
        [topVC.view removeFromSuperview];
    }
    
    // 确保视图正确附加
    if (!topVC.view.window && navController.view.window) {
        [navController.view addSubview:topVC.view];
    }
    
    // 强制布局更新
    [navController.view setNeedsLayout];
    [navController.view layoutIfNeeded];
    
    if (completion) completion();
}

- (void)recoverFromiOS12Crash:(UINavigationController *)navController {
    // iOS 12 崩溃恢复策略
    NSLog(@"🔄 iOS12: 执行崩溃恢复");
    
    // 1. 停止所有动画
    [navController.view.layer removeAllAnimations];
    [navController.navigationBar.layer removeAllAnimations];
    
    // 2. 重置视图层次
    for (UIViewController *vc in navController.viewControllers) {
        if (vc.view.superview == vc.view) {
            [vc.view removeFromSuperview];
        }
    }
    
    // 3. 强制完成转场
    [navController.view layoutIfNeeded];
    
    // 4. 回退到安全状态
    if (navController.viewControllers.count > 1) {
        [navController popToRootViewControllerAnimated:NO];
    }
}

@end

方案二:iOS 12 导航操作包装器

// 所有导航操作使用此安全包装器
- (void)safePushViewController:(UIViewController *)viewController 
                    navigation:(UINavigationController *)navigation 
                      animated:(BOOL)animated {
    
    [[iOS12NavigationProtector shared] safePerformNavigationTransition:navigation operation:^{
        // 额外的iOS 12检查
        if ([[[UIDevice currentDevice] systemVersion] containsString:@"12"]) {
            [self additionaliOS12ChecksForPush:viewController navigation:navigation];
        }
        
        @try {
            [navigation pushViewController:viewController animated:animated];
        } @catch (NSException *exception) {
            NSLog(@"❌ iOS12 Push失败: %@", exception);
            [self handleiOS12PushFailure:viewController navigation:navigation];
        }
    }];
}

- (void)additionaliOS12ChecksForPush:(UIViewController *)vc navigation:(UINavigationController *)nav {
    // iOS 12 特定检查
    
    // 检查视图控制器是否已卸载
    if (vc.isViewLoaded && vc.view.window == nil) {
        NSLog(@"⚠️ iOS12: 视图控制器视图未附加,重新加载");
        [vc viewWillAppear:NO];
        [vc viewDidAppear:NO];
    }
    
    // 检查导航栈重复
    if ([nav.viewControllers containsObject:vc]) {
        NSLog(@"⚠️ iOS12: 视图控制器已在栈中,调整策略");
        [nav popToViewController:vc animated:YES];
        return;
    }
    
    // 延迟视图加载以避免转场冲突
    if (!vc.isViewLoaded) {
        [vc loadView];
        [vc viewDidLoad];
    }
}

- (void)handleiOS12PushFailure:(UIViewController *)vc navigation:(UINavigationController *)nav {
    // 失败后的降级方案
    NSLog(@"🔄 iOS12: 使用降级导航方案");
    
    // 方案1: 延迟重试
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [nav pushViewController:vc animated:NO]; // 不使用动画
    });
    
    // 方案2: 替换当前控制器
    // NSMutableArray *newStack = [nav.viewControllers mutableCopy];
    // [newStack addObject:vc];
    // [nav setViewControllers:newStack animated:NO];
}

方案三:iOS 12 视图控制器生命周期加固

// iOS 12 专用的视图控制器基类
@interface iOS12SafeViewController : UIViewController
@property (nonatomic, assign) BOOL isiOS12;
@end

@implementation iOS12SafeViewController

- (void)viewDidLoad {
    self.isiOS12 = [[[UIDevice currentDevice] systemVersion] containsString:@"12"];
    
    [super viewDidLoad];
    
    if (self.isiOS12) {
        [self setupiOS12Protections];
    }
}

- (void)setupiOS12Protections {
    // iOS 12 特有的保护措施
    
    // 1. 强化视图加载序列化
    static dispatch_once_t viewLoadToken;
    dispatch_once(&viewLoadToken, ^{
        [self actuallySetupView];
    });
}

- (void)actuallySetupView {
    // 实际的视图设置逻辑
}

- (void)viewWillAppear:(BOOL)animated {
    if (self.isiOS12) {
        [self preiOS12AppearChecks];
    }
    
    [super viewWillAppear:animated];
}

- (void)preiOS12AppearChecks {
    // 检查视图层次状态
    if (self.view.superview == self.view) {
        NSLog(@"❌ iOS12: 检测到自添加,修复");
        [self.view removeFromSuperview];
    }
    
    // 确保视图有正确的父视图
    if (self.navigationController && !self.view.superview) {
        [self.navigationController.view addSubview:self.view];
    }
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    if (self.isiOS12) {
        [self postiOS12AppearCleanup];
    }
}

- (void)postiOS12AppearCleanup {
    // iOS 12 转场后的清理工作
    [self.view.layer removeAllAnimations];
    
    // 确保视图层次正确
    if (self.view.superview == self.view) {
        [self.view removeFromSuperview];
    }
}

@end

方案四:iOS 12 运行时保护

// iOS 12 特定的方法交换保护
@implementation iOS12RuntimeProtection

+ (void)load {
    if ([[[UIDevice currentDevice] systemVersion] containsString:@"12"]) {
        [self swizzleForiOS12];
    }
}

+ (void)swizzleForiOS12 {
    // 保护UIView的addSubview方法
    Class UIViewClass = [UIView class];
    
    SEL originalAddSubview = @selector(addSubview:);
    SEL swizzledAddSubview = @selector(iOS12_safeAddSubview:);
    
    Method originalMethod = class_getInstanceMethod(UIViewClass, originalAddSubview);
    Method swizzledMethod = class_getInstanceMethod(UIViewClass, swizzledAddSubview);
    
    if (originalMethod && swizzledMethod) {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
    // 保护导航控制器转场
    [self swizzleNavigationTransition];
}

- (void)iOS12_safeAddSubview:(UIView *)view {
    // iOS 12 安全的addSubview实现
    if (view == self) {
        NSLog(@"❌ iOS12: 阻止自添加操作");
        return;
    }
    
    if (view.superview == self) {
        NSLog(@"⚠️ iOS12: 视图已存在,跳过");
        return;
    }
    
    @try {
        [self iOS12_safeAddSubview:view];
    } @catch (NSException *exception) {
        NSLog(@"❌ iOS12: addSubview异常: %@", exception);
    }
}

+ (void)swizzleNavigationTransition {
    Class transitionClass = NSClassFromString(@"_UINavigationParallaxTransition");
    if (!transitionClass) return;
    
    SEL originalAnimate = @selector(animateTransition:);
    SEL swizzledAnimate = @selector(iOS12_safeAnimateTransition:);
    
    Method originalMethod = class_getInstanceMethod(transitionClass, originalAnimate);
    Method swizzledMethod = class_getInstanceMethod(transitionClass, swizzledAnimate);
    
    if (originalMethod && swizzledMethod) {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end

测试和验证方案

iOS 12 特定测试用例

- (void)testiOS12NavigationScenarios {
    // 模拟iOS 12的崩溃场景
    if ([[[UIDevice currentDevice] systemVersion] containsString:@"12"]) {
        [self testRapidNavigation];
        [self testViewControllerReuse];
        [self testViewHierarchyCorruption];
    }
}

- (void)testRapidNavigation {
    // 快速连续推送测试
    UINavigationController *nav = [[UINavigationController alloc] init];
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIViewController *vc = [[UIViewController alloc] init];
            [self safePushViewController:vc navigation:nav animated:YES];
        });
    }
}

总结建议

  1. 版本检测:为 iOS 12 实现特定的保护逻辑
  2. 操作序列化:确保导航操作顺序执行
  3. 状态验证:在操作前验证视图层次状态
  4. 降级方案:准备失败后的恢复策略
  5. 全面测试:在 iOS 12 设备上充分测试

iOS 12 的这个崩溃是系统版本特定的问题,通过上述针对性保护措施可以显著降低崩溃率。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • UIViewController的Present转场 这是苹果鼓励的最基本的屏幕转场方式: 我们可以做以下的一件事...
    忠橙_g阅读 4,379评论 0 1
  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,981评论 1 180
  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 8,908评论 0 6
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 6,330评论 1 4
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 5,411评论 1 3

友情链接更多精彩内容