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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容