Can't add self as subview

作者: 木夜溯 | 来源:发表于2016-01-14 14:09 被阅读4317次

    最近在iOS的项目中出现了Can't add self as subview 的crash,日志信息如下

    crash日志

    从日志上来看崩溃是在main函数,定位不到具体的地方。

    像这种crash,一般最简单地情况是:

    [self.view addSubview:self.view];

    这种确实会直接导致崩溃,但不是引起原因。

    另一种错误原因是说一次push了两次,动画被打断后引起的crash。

    头文件 实现文件

    对push的UIViewController来进行进行控制。

    另一种方法:

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

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

    #import  "UINavigationController+Consistent.h"

    #import  <objc/runtime.h>

    /// This char is used to add storage for the is PushingViewController property.

    static char const *const ObjectTagKey ="ObjectTag";

    @interfaceUINavigationController ()

    @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 originalmethod.

          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 originalmethod.

          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 originalmethod.

          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 originalmethod.

         [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

    参考文件:

    Can't add self as subview

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

    相关文章

      网友评论

      • rain__bow__:你好 如果不使用 didShowViewController:animated: 而是用代理方法 那它navigationController:didShowViewController:animated: 内容应该怎么写
      • 翀鹰精灵:大神能将这个Can't add self as subview崩溃给个demo吗,就是模拟下,我们线上的APP也收集到过这个崩溃,但是我一直没有模拟出来!
        cloud_333:不会吗 直接用代码push两次
        翀鹰精灵:@stormwyl 没有 没有解决!😂
        stormwyl:兄弟你找到demo了吗?我自己测试连续push不会崩溃呀
      • 言溪Lee:可是我出现问题的地方没有用到push-pop,用到了present,也是一样的吗?
        木夜溯:@xiaoheziQH 原理应该是一样的。
      • C_HPY:最近遇到了,终于知道原因。
        C_HPY:@上路喽 手机反应慢,卡顿等等造成的
        上路喽:请问是什么原因,能说一下吗
      • ef528bddc141:用下面Runtime的方法,发现在使用图片库选取图片的时候,- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingImage:(UIImage *)image
        editingInfo:(NSDictionary *)editingInfo这个代理方法不再执行了。这个问题有好办法吗?
        returnzyf:@木夜溯 我这里有个网页调用了系统相册,选择图片后,网页获取不到image,不加这些代码是好的
        木夜溯:@returnzyf 测试没有出现问题了的
        returnzyf:解决了吗

      本文标题:Can't add self as subview

      本文链接:https://www.haomeiwen.com/subject/hsuihttx.html