一個(gè) VC 被提前釋放的莫名 BUG

BUG情景: 重復(fù)點(diǎn)擊TabBar中的某個(gè) Item 后,當(dāng)前頁面中的 UICollectionCell 點(diǎn)擊進(jìn)入詳情頁面無效,日志顯示該詳情頁已經(jīng) Dealloc 掉了。。。

Home

類似點(diǎn)擊 重復(fù)點(diǎn)擊 Home 后,進(jìn)入不了商品詳情頁啦...

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

只要一點(diǎn)擊商品Cell,日志就顯示:

GoodsDetailViewController->>>>已經(jīng)釋放了

PS : 此處是用了一個(gè) Runtime 實(shí)現(xiàn)的:

#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(@"%@->>>>已經(jīng)釋放了",[self class]);
    [self pq_dealloc];
}

@end

一下子很悶,為什么沒有進(jìn)入 GoodsDetailViewController ,卻就被釋放掉了?


一、覺的可能是 TabBarController 那出問題了

因?yàn)楫吘勾朔N情況,只有重復(fù)點(diǎn)擊 tabbarItem 才出現(xiàn)的,于是對(duì)里面進(jìn)行檢查,發(fā)現(xiàn)就算我將里面完全復(fù)原成最基本的情況,也還是出現(xiàn)這種情況。。。

二、想著是否有 TabBarController 的Category 或者說其他的 分類有影響

在項(xiàng)目中轉(zhuǎn)了一圈,發(fā)現(xiàn)是木有的。。。

三、試著用 Present 換換 Push ,看看會(huì)有什么效果
  [self.viewController presentViewController:goodsDetailVC animated:YES completion:nil];

發(fā)現(xiàn)是可以的,然后我就在想是不是和 navigationController 有關(guān)呢? 然后就發(fā)現(xiàn)類似其他的 Push 都不可以進(jìn)入...

然后這個(gè)問題就擴(kuò)大了,凡是在 tabBar 第一個(gè)頁面中,重復(fù)點(diǎn)擊選中的 tabBarItem 后,push 相關(guān)的頁面都是失效的...

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

viewController 都會(huì)被釋放掉...

之后對(duì)該方法的攔截全部都看一遍,發(fā)現(xiàn)寫的沒問題啊,而且這個(gè)是在點(diǎn)擊之后才壞的,本來這個(gè)方法點(diǎn)擊時(shí)有效的,而且在不同 tabBarItem 可以同時(shí)呈現(xiàn)兩種不同的情況,例如在 home 中雙擊 home tabBarItem 后,該方法失效;而在Categories tabBarItem 沒有重復(fù)點(diǎn)擊卻是好的,只有重復(fù)點(diǎn)擊后才失效,顯示進(jìn)入的 viewController 已經(jīng)釋放了...

四、找到源點(diǎn)

經(jīng)過大半天后,有小伙伴在項(xiàng)目中 UINavigationController 的分類中發(fā)現(xiàn)了這個(gè)方法...

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

所以此時(shí)再回過頭來看,就是重復(fù)點(diǎn)擊了 tabbarItem 導(dǎo)致 self.isViewTransitionInProgress 變?yōu)閅ES,而無法繼續(xù)執(zhí)行該方法,從而導(dǎo)致不能 Push 過去。


重復(fù)點(diǎn)擊選中的 tabBarItem 到底為什么會(huì)讓該分類中的私有屬性變化呢?

通過日志顯示,第一次點(diǎn)擊 tabbarItem 會(huì)被 viewTransitionInProgress 默認(rèn)設(shè)置成 NO,重復(fù)點(diǎn)擊后 會(huì)直接導(dǎo)致 viewTransitionInProgress getter 方法 和 setter 方法被執(zhí)行,其中 setter 方法設(shè)置 成 YES。從而下次點(diǎn)擊pushViewController:animated:的時(shí)候,交換了方法,self.isViewTransitionInProgress == YES 就不能進(jìn)入 [self safePushViewController:viewController animated:animated]; 該方法,從而導(dǎo)致不能被執(zhí)行。

PS1: 此處 其中 setter 方法設(shè)置 成 YES 是因?yàn)樵摲诸愔械牧硪粋€(gè)方法:

- (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: 注意上面那個(gè)屬性可以得出 readwrite, 默認(rèn)讀寫的;
getter = isViewTransitionInProgress 修改了 getter 的方法。

解決

  • 1、注釋剛才那個(gè)setter 重新設(shè)置成YES 的方法:
//    if (animated) {
//        self.viewTransitionInProgress = YES;
//    }

個(gè)人認(rèn)為在 rootViewController,viewTransitionInProgress 應(yīng)該默認(rèn)是NO,暫時(shí)無發(fā)現(xiàn)有什么問題。

  • 2、另外就是直接干掉這個(gè)分類,因?yàn)閷?shí)際上在該項(xiàng)目中它這個(gè)分類用處不是很大,是起預(yù)防的,所以也可以直接不用它。

總的說來,下次遇到類似的問題,可以到分類中看看,有沒有用到 Runtime 的方法。
另外用Runtime 系列方法也一定要謹(jǐn)慎!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容