这个崩溃在 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];
});
}
}
总结建议
- 版本检测:为 iOS 12 实现特定的保护逻辑
- 操作序列化:确保导航操作顺序执行
- 状态验证:在操作前验证视图层次状态
- 降级方案:准备失败后的恢复策略
- 全面测试:在 iOS 12 设备上充分测试
iOS 12 的这个崩溃是系统版本特定的问题,通过上述针对性保护措施可以显著降低崩溃率。