BUG情景: 重复点击TabBar中的某个 Item 后,当前页面中的 UICollectionCell 点击进入详情页面无效,日志显示该详情页已经 Dealloc 掉了。。。
类似点击 重复点击 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 系列方法也一定要谨慎!