項(xiàng)目中bugly總是收集到Can't add self as subview 的崩潰
錯(cuò)誤,崩潰調(diào)用堆棧解析如下:

通過分析崩潰堆棧日志解析和崩潰信息提示,只能得出兩個(gè)線索:
1.addSubView的參數(shù)是自己本身self;
2.崩潰和navigationController轉(zhuǎn)場(chǎng)動(dòng)畫有關(guān)。
排查
1.addSubView的參數(shù)是自己本身self
在工程中用 [self addSubView:self] 測(cè)試,確實(shí)會(huì)崩潰,崩潰的堆棧信息如下。和上傳的崩潰信息不一致,可以排除。

2.navigationController轉(zhuǎn)場(chǎng)動(dòng)畫
navigationController push或者pop上一次操作還沒有完成就開始執(zhí)行下一次操作,同步執(zhí)行了多個(gè)轉(zhuǎn)場(chǎng)操作。如下代碼:
[self.navigationController pushViewController:[[SecondAViewController alloc] init] animated:NO];
[self.navigationController pushViewController:[[SecondBViewController alloc] init] animated:YES];
[self.navigationController pushViewController:[[SecondCViewController alloc] init] animated:YES];
同時(shí)執(zhí)行完以上操作,之后的pop退場(chǎng)操作就會(huì)導(dǎo)致崩潰。崩潰信息如下:

push SecondCViewController,C成功入棧,但是視圖沒有加載到容器中,實(shí)際顯示的還是B的vc與view,但是棧頂是C的vc。
第一次點(diǎn)擊返回時(shí),實(shí)際是C出棧,但是當(dāng)前顯示的視圖B的view被先加載到formAnimateView與toAnimateView上,原本視圖在出棧后應(yīng)該被釋放,但是現(xiàn)在容器棧內(nèi)還存在B的vc;
當(dāng)?shù)诙吸c(diǎn)擊返回時(shí),實(shí)際應(yīng)該是B的vc出棧,但是A的view加載到toAnimateView上之后,toAnimateView需要加載到wrapperView進(jìn)行transition動(dòng)畫, 但wrapperView通過棧頂元素view.superview取值, 而棧頂元素B的view由于上一次錯(cuò)誤的轉(zhuǎn)場(chǎng), 并未在transition動(dòng)畫完成后掛載到wrapperView, 還保留在的臨時(shí)的動(dòng)畫視圖toAnimateView上, 所以使toAnimateView加載到WrapperView的操作變成了動(dòng)畫視圖toAnimateView加載到自己上
解決方法
導(dǎo)致轉(zhuǎn)場(chǎng)異常的根本原因是上一次操作還沒執(zhí)行結(jié)束就開始執(zhí)行下一次操作, 同步執(zhí)行了多個(gè)轉(zhuǎn)場(chǎng)操作,這時(shí)就需要攔截控制器入棧\出棧的方法,確保當(dāng)有控制器正在進(jìn)行入棧\出棧的操作時(shí),沒有其他入棧\出棧操作。
實(shí)現(xiàn)
通過Runtime的方法魔法Method Swizzling技術(shù)實(shí)現(xiàn)。分類實(shí)現(xiàn)修改navigationControlle的pop和push方法。在push\pop方法中設(shè)置一個(gè)標(biāo)志位,動(dòng)畫結(jié)束之后,重置標(biāo)志位,通過標(biāo)志位來判斷push\pop操作是否執(zhí)行。代碼實(shí)現(xiàn)如下:
@interface UINavigationController () <UINavigationControllerDelegate>
@property (nonatomic, assign) BOOL viewTransitionInProgress;
@end
@implementation UINavigationController (SafeTransition)
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(pushViewController:animated:)),
class_getInstanceMethod(self, @selector(safePushViewController: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:)));
}
#pragma mark - setter & getter
- (void)setViewTransitionInProgress:(BOOL)property {
NSNumber *number = [NSNumber numberWithBool:property];
objc_setAssociatedObject(self, @selector(viewTransitionInProgress), number, OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)viewTransitionInProgress {
NSNumber *number = objc_getAssociatedObject(self, @selector(viewTransitionInProgress));
return [number boolValue];
}
#pragma mark - Intercept Pop, Push, PopToRootVC
- (NSArray *)safePopToRootViewControllerAnimated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated) {
self.viewTransitionInProgress = YES;
}
NSArray *viewControllers = [self safePopToRootViewControllerAnimated:animated];
if (viewControllers.count == 0) {
self.viewTransitionInProgress = NO;
}
return viewControllers;
}
- (NSArray *)safePopToViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated){
self.viewTransitionInProgress = YES;
}
NSArray *viewControllers = [self safePopToViewController:viewController animated:animated];
if (viewControllers.count == 0) {
self.viewTransitionInProgress = NO;
}
return viewControllers;
}
- (UIViewController *)safePopViewControllerAnimated:(BOOL)animated {
if (self.viewTransitionInProgress) return nil;
if (animated) {
self.viewTransitionInProgress = YES;
}
UIViewController *viewController = [self safePopViewControllerAnimated:animated];
if (viewController == nil) {
self.viewTransitionInProgress = NO;
}
return viewController;
}
- (void)safePushViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.viewTransitionInProgress == NO) {
[self safePushViewController:viewController animated:animated];
if (animated) {
self.viewTransitionInProgress = YES;
}
}
}
@end
@implementation UIViewController (SafeTransitionLock)
+ (void)load {
Method m1;
Method m2;
m1 = class_getInstanceMethod(self, @selector(safeViewDidAppear:));
m2 = class_getInstanceMethod(self, @selector(viewDidAppear:));
method_exchangeImplementations(m1, m2);
}
- (void)safeViewDidAppear:(BOOL)animated {
self.navigationController.viewTransitionInProgress = NO;
[self safeViewDidAppear:animated];
}
@end