Can't Add Self as Subview 崩溃解决办法

简介:

前言

查看项目的崩溃汇总,一直有一个 bug 存在,之前也有注意过,只因这个 bug 的崩溃堆栈日志解析出来并没有什么卵用,也不知该怎么复现,就撂下了。

但现在这个 bug 崩溃发生次数蹭蹭涨,崩溃占比高了起来,不得不重视起来。

点进崩溃详情,只有短短的一句 Can't add self as subview

崩溃调用堆栈解析出来如下:

崩溃调用堆栈解析

对解决问题根本起不了作用,只能依靠 Google 了。

bug 复现

在 StackOverflow 上找到复现此 bug 的方法,尝试同时 push 两个控制器,或者同时 push 和 pop一个控制器。

测试同时 push 两个控制器

上图是当时一回答此问题人的运行结果,但现在测试并不会引起崩溃,不过同时 push 一个控制器是会导致崩溃的,测试结果如下图:

测试同时 push 一个控制器

其实这个崩溃,最简单的情况就是:

1
[self.view addSubview:self.view];

而这个原因就是同一时间同时 push 多个控制器再返回,动画被打断后引起的崩溃,本质根源是 push 动画还没有完成就急着 push 下一个控制器。

解决办法

创建一个分类,拦截控制器入栈\出栈的方法调用,通过安全的方式,确保当有控制器正在进行入栈\出栈操作时,没有其他入栈\出栈操作。

此分类用到运行时 (Runtime) 的方法交换 Method Swizzling,因此只需要复制下面的代码到自己的项目中,此 bug 就不复存在了。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
#import "UINavigationController+Consistent.h"#import <objc/runtime.h>/// 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<UIViewControllerTransitionCoordinator> tc = navigationController.topViewController.transitionCoordinator;    [tc notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {        self.viewTransitionInProgress = NO;        //--Reenable swipe back gesture.        self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)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

原始地址: http://lengmolehongyan.github.io/blog/2015/10/30/cant-add-self-as-subview-beng-kui-jie-jue-ban-fa/
written by 冷漠叻荭颜  posted at http://lengmolehongyan.github.io

 2015 年 10 月 30 日 11:14 pm
















本文转自ljianbing51CTO博客,原文链接:http://blog.51cto.com/ljianbing/1791360 ,如需转载请自行联系原作者



相关文章
|
12月前
|
API Android开发
fragment不断切换app崩溃的解决办法
fragment不断切换app崩溃的解决办法
|
Java Linux Windows
记一次因类未加载导致DEBUG断点执行不了的“诡异”
记一次因类未加载导致DEBUG断点执行不了的“诡异”
85 0
|
移动开发 iOS开发
iOS WKWebView h5使用alert方法不起作用解决方法
iOS WKWebView h5使用alert方法不起作用解决方法
447 0
|
Java 数据库 Android开发
Android 7.1 异常删除data文件导致HOME键失效
Android 7.1 异常删除data文件导致HOME键失效
159 0
|
iOS开发
iOS开发 - 关于MJRefresh刷新崩溃的问题
iOS开发 - 关于MJRefresh刷新崩溃的问题
145 0
|
安全 算法
代码还原的技术: Unidbg hook_add_new实现条件断点(二)
代码还原的技术: Unidbg hook_add_new实现条件断点(二)
代码还原的技术: Unidbg hook_add_new实现条件断点(二)
关于 QMessageBox定制大小重写showEvent失败的 解决方法
关于 QMessageBox定制大小重写showEvent失败的 解决方法
关于 QMessageBox定制大小重写showEvent失败的 解决方法
loaded the "xxx" nib but the view outlet was not set 错误的解决办法。
loaded the "xxx" nib but the view outlet was not set 错误的解决办法。
188 0
popupwindow showAsDropDown 无效解决方法
popupwindow showAsDropDown 无效解决方法
popupwindow showAsDropDown 无效解决方法
|
监控 调度
04.Android崩溃Crash库之Loop拦截崩溃和ANR
04.Android崩溃Crash库之Loop拦截崩溃和ANR
836 0