返回
iOS屏幕旋转后崩溃的定位过程
IOS
2023-10-23 11:14:09
引言
屏幕旋转是iOS开发中经常遇到的一个问题,特别是当使用第三方库时,很容易出现屏幕旋转后崩溃的情况。本文详细记录了一次在iOS上,点击按钮进行屏幕旋转后发生的崩溃的定位过程,希望能对其他开发人员有所帮助。
问题复现
问题触发:点击按钮进行屏幕旋转发生了崩溃。
一般这个时候只要查看调用栈信息就可以定位到崩溃的原因,但是这里的调用栈信息只能看到强制屏幕旋转的代码。这种强制屏幕旋转的方式在很多第三方库中都有使用到。
定位过程
- 首先,查看崩溃日志。
在崩溃日志中,我们可以看到以下信息:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update operation with no current view controller.'
从崩溃日志中可以看出,崩溃是由于NSInternalInconsistencyException
异常引起的。这个异常通常是由于在没有当前视图控制器的情况下进行更新操作时发生的。
- 接下来,查看调用栈信息。
在调用栈信息中,我们可以看到以下内容:
#0 0x000000010e422109 in __49-[UIWindow _setDeviceOrientation:updateStatusBarOrientation:] ()
#1 0x000000010e42200e in -[UIWindow _setDeviceOrientation:updateStatusBarOrientation:] ()
#2 0x000000010e421f2c in -[UIWindow _updateRotationAnimationWithTargetOrientation:] ()
#3 0x000000010e422393 in -[UIWindow setRotation:toOrientation:updateStatusBar:] ()
#4 0x0000000112f7b1b5 in -[UIViewControllerWrapperView transitionFromView:toView:info:] ()
#5 0x0000000112f7b0a3 in -[UIViewControllerWrapperView replaceSubview:withSubview:usingTransition:completion:] ()
#6 0x0000000112f79633 in -[UIViewControllerWrapperView setSubviews:] ()
#7 0x0000000112f73a97 in -[UIViewControllerWrapperView presentViewController:usingTransition:complete:] ()
#8 0x0000000112f8003a in -[UIViewController presentViewController:animated:completion:] ()
#9 0x0000000112f8031e in -[UIViewController presentViewController:animated:] ()
#10 0x000000010e70f5fb in -[UIRotationGestureRecognizer _handleGesture:] ()
从调用栈信息中可以看出,崩溃发生在UIViewController
的presentViewController:
方法中。这个方法用于以动画的方式在当前视图控制器上呈现一个新的视图控制器。
- 接下来,查看
UIViewController
的presentViewController:
方法的实现。
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
NSAssert(viewControllerToPresent != nil, @"Attempt to present nil view controller");
if (viewControllerToPresent == self) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Cannot present a view controller onto itself!"];
return;
}
if (self.presentedViewController != nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Presenting view controller (%@) has a presented view controller. You must dismiss it before presenting a new one.", self];
return;
}
if (![self isViewLoaded]) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Presenting view controller (%@) has not yet been loaded. You must load it before presenting it.", self];
return;
}
if (self.view == nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ whose view is nil. This is probably because it has not yet been added to its parent.", viewControllerToPresent, self];
return;
}
if (!self.view.window) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ whose view has no window. This is probably because it has not yet been added to its parent.", viewControllerToPresent, self];
return;
}
if (viewControllerToPresent.view == nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ whose view is nil. You must set its view before presenting it.", viewControllerToPresent, self];
return;
}
if (viewControllerToPresent.presentingViewController) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ which is already presenting %@.", viewControllerToPresent, self, viewControllerToPresent.presentingViewController];
return;
}
if (viewControllerToPresent.transitioningDelegate == nil) {
viewControllerToPresent.transitioningDelegate = self;
}
viewControllerToPresent.presentingViewController = self;
[self _beginTransitionToPresentedViewController:viewControllerToPresent withTransitionContext:nil];
if (flag) {
[self _animateTransitionToPresentedViewController:viewControllerToPresent withTransitionContext:nil];
} else {
[self _completeTransitionToPresentedViewController:viewControllerToPresent withTransitionContext:nil];
}
if (completion) {
[viewControllerToPresent _addDelegateForTransitionCompletion:self completion:completion];
}
}
从UIViewController
的presentViewController:
方法的实现中可以看出,在呈现一个新的视图控制器之前,需要检查当前视图控制器是否已经加载完毕,是否有窗口,以及是否已经呈现了另一个视图控制器。
- 接下来,检查当前视图控制器是否已经加载完毕。
if (![self isViewLoaded]) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Presenting view controller (%@) has not yet been loaded. You must load it before presenting it.", self];
return;
}
通过检查,发现当前视图控制器已经加载完毕。
- 接下来,检查当前视图控制器是否有窗口。
if (self.view == nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ whose view is nil. This is probably because it has not yet been added to its parent.", viewControllerToPresent, self];
return;
}
通过检查,发现当前视图控制器有窗口。
- 接下来,检查当前视图控制器是否已经呈现了另一个视图控制器。
if (self.presentedViewController != nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Presenting view controller (%@) has a presented view controller. You must dismiss it before presenting a new one.", self];
return;
}
通过检查,发现当前视图控制器没有呈现另一个视图控制器。
- 接下来,检查新的视图控制器是否已经加载完毕。
if (viewControllerToPresent.view == nil) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ whose view is nil. You must set its view before presenting it.", viewControllerToPresent, self];
return;
}
通过检查,发现新的视图控制器已经加载完毕。
- 接下来,检查新的视图控制器是否已经呈现了另一个视图控制器。
if (viewControllerToPresent.presentingViewController) {
[NSException raise:@"NSInternalInconsistencyException" format:@"Attempting to present %@ on %@ which is already presenting %@.", viewControllerToPresent, self, viewControllerToPresent.presentingViewController];
return;
}
通过检查,发现新的视图控制器没有呈现另一个视图控制器。
- 接下来,检查新的视图控制器的过渡委托是否为空。
if (viewControllerToPresent.transitioningDelegate == nil) {
viewControllerToPresent.transitioningDelegate = self;
}
通过检查,发现新的视图控制器的过渡委托不为空。
- 接下来,设置新的视图控制器的呈现视图控制器。
viewControllerToPresent.presentingViewController = self;
- 接下来,开始向新的视图控制器过渡。
[