题记:疫情无情 人有情 人间值得
实现思路:
NSObject+Swizzle 实现方法交换;UIViewController+hook实现将viewController生命周期方法交换;UINavigationController+hook实现popViewController交换.
实现过程如下
NSObject+Swizzle.h 中申明
+ (void)swizzleMethod:(SEL)originalSEL swizzleSelector:(SEL)SwizzleSEL;
NSObject+Swizzle.m 中实现方法交换
+ (void)swizzleMethod:(SEL)originalSEL swizzleSelector:(SEL)SwizzleSEL{
Class class = [self class];
Method originalMthod = class_getInstanceMethod(class, originalSEL);
Method swizzleMethod = class_getInstanceMethod(class, SwizzleSEL);
IMP originalIMP = method_getImplementation(originalMthod);
IMP swizzleIMP = method_getImplementation(swizzleMethod);
//是否t添加成功g方法:添加了原始方法 实现内容指向目标方法
BOOL isAdd = class_addMethod(class, originalSEL, swizzleIMP, method_getTypeEncoding(swizzleMethod));
if (isAdd) {
//初始化指向目标,那么把目标的内容指向初始
class_replaceMethod(class, SwizzleSEL, originalIMP, method_getTypeEncoding(originalMthod));
}else{
//没有添加成功说明已经存在,就交换
//注意这里交换实现的是IMP 交换
method_exchangeImplementations(originalMthod, swizzleMethod);
}
}
UIViewController+hook.m 中
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleMethod:@selector(viewWillAppear:) swizzleSelector:@selector(lg_viewWillAppear:)];
[self swizzleMethod:@selector(viewDidDisappear:) swizzleSelector:@selector(lg_viewDidDisappear:)];
});
}
增加一个场字符串popFlag变量 用以标记视图控制器pop状态
const char *popFlag = "popFlag";
lg_viewWillAppear方法中 运行时增加一个BOOL变量默认赋值为NO
- (void)lg_viewWillAppear:(BOOL)animated{
[self lg_viewWillAppear:animated];
objc_setAssociatedObject(self, popFlag, @(NO), OBJC_ASSOCIATION_ASSIGN);
}
lg_viewDidDisappear方法中 判断BOOL变量 是否为YES 如果为YES 就开启内存检测
- (void)lg_viewDidDisappear:(BOOL)animated{
[self lg_viewWillAppear:animated];
if ([objc_getAssociatedObject(self, popFlag) boolValue]) {
[self lg_dealloc];
}
}
- (void)lg_dealloc{
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof (self) strongSelf = weakSelf;
NSLog(@"lg_dealloc----%@",strongSelf);
});
}
UINavigationController+hook.m中
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleMethod:@selector(popViewControllerAnimated:) swizzleSelector:@selector(lg_popViewControllerAnimated:)];
});
}
- (UIViewController *)lg_popViewControllerAnimated:(BOOL)animated{
UIViewController *popVC = [self lg_popViewControllerAnimated:animated];
//导入外部变量
extern const char *popFlag ;
objc_setAssociatedObject(popVC, popFlag, @(YES), OBJC_ASSOCIATION_ASSIGN);
return popVC;
}
四个典型的直接使用 Runtime 方法进行方法交换的风险。我稍作整理,以方便你查看,并便于你理解后续的内容。
第一个风险是,需要在 +load 方法中进行方法交换。因为如果在其他时候进行方法交换,难以保证另外一个线程中不会同时调用被交换的方法,从而导致程序不能按预期执行。
第二个风险是,被交换的方法必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须在调用的时候使用,而不是方法交换时使用。
第三个风险是,交换的方法如果依赖了 cmd,那么交换后,如果 cmd 发生了变化,就会出现各种奇怪问题,而且这些问题还很难排查。特别是交换了系统方法,你无法保证系统方法内部是否依赖了 cmd。
第四个风险是,方法交换命名冲突。如果出现冲突,可能会导致方法交换失败。