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

類似點(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)慎!