前言
查看项目的崩溃汇总,一直有一个 bug 存在,之前也有注意过,只因这个 bug 的崩溃堆栈日志解析出来并没有什么卵用,也不知该怎么复现,就撂下了。
但现在这个 bug 崩溃发生次数蹭蹭涨,崩溃占比高了起来,不得不重视起来。
点进崩溃详情,只有短短的一句 Can't add self as subview
。
崩溃调用堆栈解析出来如下:
对解决问题根本起不了作用,只能依靠 Google 了。
bug 复现
在 StackOverflow 上找到复现此 bug 的方法,尝试同时 push
两个控制器,或者同时 push
和 pop
一个控制器。
上图是当时一回答此问题人的运行结果,但现在测试并不会引起崩溃,不过同时 push
一个控制器是会导致崩溃的,测试结果如下图:
其实这个崩溃,最简单的情况就是:
1 |
[self.view addSubview:self.view]; |
而这个原因就是同一时间同时 push 多个控制器再返回,动画被打断后引起的崩溃,本质根源是 push 动画还没有完成就急着 push 下一个控制器。
解决办法
创建一个分类,拦截控制器入栈\出栈的方法调用,通过安全的方式,确保当有控制器正在进行入栈\出栈操作时,没有其他入栈\出栈操作。
此分类用到运行时 (Runtime) 的方法交换 Method Swizzling
,因此只需要复制下面的代码到自己的项目中,此 bug 就不复存在了。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
#import "UINavigationController+Consistent.h"#import /// This char is used to add storage for the isPushingViewController property.static char const * const ObjectTagKey = "ObjectTag";@interface UINavigationController ()@property (readwrite,getter = isViewTransitionInProgress) BOOL viewTransitionInProgress;@end@implementation UINavigationController (Consistent)- (void)setViewTransitionInProgress:(BOOL)property { NSNumber *number = [NSNumber numberWithBool:property]; objc_setAssociatedObject(self, ObjectTagKey, number , OBJC_ASSOCIATION_RETAIN);}- (BOOL)isViewTransitionInProgress { NSNumber *number = objc_getAssociatedObject(self, ObjectTagKey); return [number boolValue];}#pragma mark - Intercept Pop, Push, PopToRootVC/// @name Intercept Pop, Push, PopToRootVC- (NSArray *)safePopToRootViewControllerAnimated:(BOOL)animated { if (self.viewTransitionInProgress) return nil; if (animated) { self.viewTransitionInProgress = YES; } //-- This is not a recursion, due to method swizzling the call below calls the original method. return [self safePopToRootViewControllerAnimated:animated];}- (NSArray *)safePopToViewController:(UIViewController *)viewController animated:(BOOL)animated { if (self.viewTransitionInProgress) return nil; if (animated) { self.viewTransitionInProgress = YES; } //-- This is not a recursion, due to method swizzling the call below calls the original method. return [self safePopToViewController:viewController animated:animated];}- (UIViewController *)safePopViewControllerAnimated:(BOOL)animated { if (self.viewTransitionInProgress) return nil; if (animated) { self.viewTransitionInProgress = YES; } //-- This is not a recursion, due to method swizzling the call below calls the original method. return [self safePopViewControllerAnimated:animated];}- (void)safePushViewController:(UIViewController *)viewController animated:(BOOL)animated { self.delegate = self; //-- If we are already pushing a view controller, we dont push another one. if (self.isViewTransitionInProgress == NO) { //-- This is not a recursion, due to method swizzling the call below calls the original method. [self safePushViewController:viewController animated:animated]; if (animated) { self.viewTransitionInProgress = YES; } }}// This is confirmed to be App Store safe.// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.- (void)safeDidShowViewController:(UIViewController *)viewController animated:(BOOL)animated { //-- This is not a recursion. Due to method swizzling this is calling the original method. [self safeDidShowViewController:viewController animated:animated]; self.viewTransitionInProgress = NO;}// If the user doesnt complete the swipe-to-go-back gesture, we need to intercept it and set the flag to NO again.- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { id tc = navigationController.topViewController.transitionCoordinator; [tc notifyWhenInteractionEndsUsingBlock:^(id context) { self.viewTransitionInProgress = NO; //--Reenable swipe back gesture. self.interactivePopGestureRecognizer.delegate = (id)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; }]; //-- Method swizzling wont work in the case of a delegate so: //-- forward this method to the original delegate if there is one different than ourselves. if (navigationController.delegate != self) { [navigationController.delegate navigationController:navigationController willShowViewController:viewController animated:animated]; }}+ (void)load { //-- Exchange the original implementation with our custom one. method_exchangeImplementations(class_getInstanceMethod(self, @selector(pushViewController:animated:)), class_getInstanceMethod(self, @selector(safePushViewController:animated:))); method_exchangeImplementations(class_getInstanceMethod(self, @selector(didShowViewController:animated:)), class_getInstanceMethod(self, @selector(safeDidShowViewController:animated:))); method_exchangeImplementations(class_getInstanceMethod(self, @selector(popViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopViewControllerAnimated:))); method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToRootViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopToRootViewControllerAnimated:))); method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToViewController:animated:)), class_getInstanceMethod(self, @selector(safePopToViewController:animated:)));}@end |
written by 冷漠叻荭颜 posted at http://lengmolehongyan.github.io
2015 年 10 月 30 日 11:14 pm
本文转自ljianbing51CTO博客,原文链接:http://blog.51cto.com/ljianbing/1791360 ,如需转载请自行联系原作者