一个 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 <objc/runtime.h>
@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 () <UINavigationControllerDelegate>

@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 系列方法也一定要谨慎!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容