一个 VC 被提前释放的莫名 BUG

BUG情景: 重复点击TabBar中的某个 Item 后,当前页面中的 UICollectionCell 点击进入详情页面无效,日志显示该详情页已经 Dealloc 掉了。。。

Home

类似点击 重复点击 Home 后,进入不了商品详情页啦...

GoodsDetailViewController *goodsDetailVC = [[GoodsDetailViewController alloc] init];
goodsDetailVC.goodsId = goodsModel.goodsId;
[self.viewController.navigationController pushViewController:goodsDetailVC animated:YES];

只要一点击商品Cell,日志就显示:

GoodsDetailViewController->>>>已经释放了

PS : 此处是用了一个 Runtime 实现的:

#import "UIViewController+Dealloc.h"
#import 
@implementation UIViewController (Dealloc)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        Method setTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"dealloc"));
        Method replaceSetTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"pq_dealloc"));
        method_exchangeImplementations(setTextMethod, replaceSetTextMethod);
    });
}

- (void)pq_dealloc {
    NSLog(@"%@->>>>已经释放了",[self class]);
    [self pq_dealloc];
}

@end

一下子很闷,为什么没有进入 GoodsDetailViewController ,却就被释放掉了?


一、觉的可能是 TabBarController 那出问题了

因为毕竟此种情况,只有重复点击 tabbarItem 才出现的,于是对里面进行检查,发现就算我将里面完全复原成最基本的情况,也还是出现这种情况。。。

二、想着是否有 TabBarController 的Category 或者说其他的 分类有影响

在项目中转了一圈,发现是木有的。。。

三、试着用 Present 换换 Push ,看看会有什么效果
  [self.viewController presentViewController:goodsDetailVC animated:YES completion:nil];

发现是可以的,然后我就在想是不是和 navigationController 有关呢? 然后就发现类似其他的 Push 都不可以进入...

然后这个问题就扩大了,凡是在 tabBar 第一个页面中,重复点击选中的 tabBarItem 后,push 相关的页面都是失效的...

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated

viewController 都会被释放掉...

之后对该方法的拦截全部都看一遍,发现写的没问题啊,而且这个是在点击之后才坏的,本来这个方法点击时有效的,而且在不同 tabBarItem 可以同时呈现两种不同的情况,例如在 home 中双击 home tabBarItem 后,该方法失效;而在Categories tabBarItem 没有重复点击却是好的,只有重复点击后才失效,显示进入的 viewController 已经释放了...

四、找到源点

经过大半天后,有小伙伴在项目中 UINavigationController 的分类中发现了这个方法...

+ (void)load {
    method_exchangeImplementations(
                class_getInstanceMethod(self, @selector(pushViewController:animated:)), 
                class_getInstanceMethod(self, @selector(safePushViewController: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;
        }
    }
}
@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];
}

所以此时再回过头来看,就是重复点击了 tabbarItem 导致 self.isViewTransitionInProgress 变为YES,而无法继续执行该方法,从而导致不能 Push 过去。


重复点击选中的 tabBarItem 到底为什么会让该分类中的私有属性变化呢?

通过日志显示,第一次点击 tabbarItem 会被 viewTransitionInProgress 默认设置成 NO,重复点击后 会直接导致 viewTransitionInProgress getter 方法 和 setter 方法被执行,其中 setter 方法设置 成 YES。从而下次点击pushViewController:animated:的时候,交换了方法,self.isViewTransitionInProgress == YES 就不能进入 [self safePushViewController:viewController animated:animated]; 该方法,从而导致不能被执行。

PS1: 此处 其中 setter 方法设置 成 YES 是因为该分类中的另一个方法:

- (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];
}

PS2: 注意上面那个属性可以得出 readwrite, 默认读写的;
getter = isViewTransitionInProgress 修改了 getter 的方法。

解决

  • 1、注释刚才那个setter 重新设置成YES 的方法:
//    if (animated) {
//        self.viewTransitionInProgress = YES;
//    }

个人认为在 rootViewController,viewTransitionInProgress 应该默认是NO,暂时无发现有什么问题。

  • 2、另外就是直接干掉这个分类,因为实际上在该项目中它这个分类用处不是很大,是起预防的,所以也可以直接不用它。

总的说来,下次遇到类似的问题,可以到分类中看看,有没有用到 Runtime 的方法。
另外用Runtime 系列方法也一定要谨慎!

你可能感兴趣的:(一个 VC 被提前释放的莫名 BUG)