在项目开发过程中,总是能遇到一些重复代码,却不得不一遍遍重写,有些还特别容易产生变动,然后你又不得不一个个找到它们,并手动一个个去修改,很容易会出现遗漏或者手残改错的情况。或许使用继承在基类中书写共同的方法可以在一定程度上减少代码修改的过程,可是并不能保证所有的控制器都继承自同一个基类,而且继承会产生很多不必要的内存冗余。这时候我们就可以尝试使用运行时机制动态交换方法的实现。
下面就以拦截UIViewController类viewWillAppear:方法,并在原始方法执行之前执行我们定义的操作为例,根据被替换方法实现的书写形式形式进行划分来进行介绍.
替换方式
1. 使用OC的方法来做方法交换
我们可以使用自己定义的新方法来交换原来的实现,从而在原来的实现上做一些自己的处理.
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class destinationClass = [UIViewController class]; Method method_viewWillAppear_orignal = class_getInstanceMethod(destinationClass, @selector(viewWillAppear:)); Method method_vieWillAppear_replace = class_getInstanceMethod(destinationClass, @selector(ed_viewWillAppear:)); method_exchangeImplementations(method_viewWillAppear_orignal, method_vieWillAppear_replace); }); } - (void)ed_viewWillAppear:(BOOL)animated { //自定义操作 [EDRecordManager enterViewController:NSStringFromClass(self.class)]; //调用原来的方法实现 [self ed_viewWillAppear:animated]; }
这样,我们就将系统的viewWillAppear:和我们自定义的ed_viewWillAppear:进行了交换,可以在方法执行之前加入自定义的操作,需要注意的是:
- 这个交换不一定要写在load方法中,在线程安全和确保不交换多次的情况下,可以写在你需要的任何地方;
- 方法完成交换之后,两个方法名称指向的实现已经发生了变化,所以以下代码调用并不会造成循环调用,而是调用了原始的方法实现(在相当多的情形下,我们需要原来的调用实现,尤其在不明确系统实现的情况下).
[self ed_viewWillAppear:animated];
2. 使用C语言的方法进行交换
当使用C语言的函数进行方法交换时,需要注意:
- 每个C语言函数至少有两个参数,即默认的方法调用者(receiver)和方法选择器(selector);
- 如果需要调用原始实现,在方法实现交换之前必须通过变量保存原始的实现.
所以,如果用C语言实现上面的交换,差不多是这个样子:
void(*orignalImplement)(id self, SEL cmd); static void ed_viewWillAppear(id self, SEL cmd) { [EDRecordManager enterViewController:NSStringFromClass([self class])]; if (orignalImplement) { orignalImplement(self, cmd); } } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class destinationClass = [UIViewController class]; SEL sel = @selector(viewWillAppear:); Method method_viewWillAppear_orignal = class_getInstanceMethod(destinationClass, sel); //保存原始实现 orignalImplement = (typeof(orignalImplement))class_getMethodImplementation(destinationClass, sel); method_setImplementation(method_viewWillAppear_orignal, (IMP)ed_viewWillAppear); }); return YES; }
在上述实现中,我们使用全局变量orignalImplement来保存原始的方法实现,这样就可以在我们自定义的方法中,调用原来方法的实现.
3. 使用Block方法进行交换
Block是比较特殊的存在,它的默认参数和c语言不太一样,它只有一个默认的参数,用来接收方法的执行接受者,所以需要捕获外部的方法选择器变量.同样我们需要保存原始的实现来共后续的调用,大概这个样子:
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class destinationClass = [UIViewController class]; SEL sel = @selector(viewWillAppear:); Method method_viewWillAppear_orignal = class_getInstanceMethod(destinationClass, sel); __block void(*orignalImplement)(__unsafe_unretained id _self, SEL _cmd) = NULL; void(^block)(id) = ^ (id _self){ [EDRecordManager enterViewController:NSStringFromClass([self class])]; if (orignalImplement) { orignalImplement(_self, sel); } }; IMP imp = imp_implementationWithBlock(block); orignalImplement = (typeof(orignalImplement))method_getImplementation(method_viewWillAppear_orignal); method_setImplementation(method_viewWillAppear_orignal, imp); });
其他
这里有一个小问题:
在OC这门语言中,方法实现中的self是不是一定是方法所在类或者类对象呢?
答案是,不是.
例如:在微信登陆界面,想要hook掉注册按钮的点击事件,进行了如下操作:
#import "InjectCode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation InjectCode
+ (void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
Method method_onFirstViewRegister = class_getInstanceMethod(class, @selector(onFirstViewRegister));
Method method_EW_onFirstViewRegister = class_getInstanceMethod(self, @selector(EW_onFirstViewRegister));
method_exchangeImplementations(method_onFirstViewRegister, method_EW_onFirstViewRegister);
}
- (void)EW_onFirstViewRegister {
NSLog(@"self == %@, _cmd == %@", self, NSStringFromSelector(_cmd));
}
@end
然后点击登陆界面的注册按钮进行测试,发现:
self == <WCAccountLoginControlLogic: 0x282bf0640>, _cmd == onFirstViewRegister
当前的self,并不是InjectCode的对象,而_cmd也并不是EW_onFirstViewRegister这个SEL.或者这时候就可以知道为什么在多数情况下会选择在对应类的Category中进行方法hook,而不是独立的类中进行:在Category中进行方法交换,可以保证无论是交换之前还是交换之后,都可以保证当前self就是当前类的对象,交换之前的实现和交换之后的实现都在当前类的方法列表中,不容易出找不到方法实现的异常.
那么如果真的同过上边的方法实现了方法交换,如何在交换之后的实现中调用原来的方法实现呢?其实也很简单,方法交换之后,原来的方法实现被保存在了InjectCode中EW_onFirstViewRegister对应的的实现地址中,所以只需要在InjectCode类中查找到方法实现,进行调用即可.
- (void)EW_onFirstViewRegister {
//1. 找到原始的方法实现
IMP imp = class_getMethodImplementation([InjectCode class], @selector(EW_onFirstViewRegister));
//2. 强制转化方法的参数类型
void (*_imp)(id, SEL) = (void(*)(id, SEL))imp;
//3. 调用方法原来实现
_imp(self, _cmd);
//其中,步骤2,3可以合并在一起
//((void(*)(id, SEL))imp)(self, _cmd);
}
除此之外,为了在交换之后的方法中方便调用原来的实现,还可以采用以下思路:
- 使用指针保存原始函数实现;
#import "InjectCode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation InjectCode
void (*ori_onFirstViewRegister)(id, SEL);
+ (void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
Method method_onFirstViewRegister = class_getInstanceMethod(class, @selector(onFirstViewRegister));
//保存原始实现
ori_onFirstViewRegister = (void(*)(id, SEL))method_getImplementation(method_onFirstViewRegister);
Method method_EW_onFirstViewRegister = class_getInstanceMethod(self, @selector(EW_onFirstViewRegister));
method_exchangeImplementations(method_onFirstViewRegister, method_EW_onFirstViewRegister);
}
- (void)EW_onFirstViewRegister {
if(ori_onFirstViewRegister) {
ori_onFirstViewRegister(self, _cmd);
}
}
@end
- 使用类的Categoty进行方法交换
#import "InjectCode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation WCAccountLoginControlLogic (Exchange)
+ (void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
Method method_onFirstViewRegister = class_getInstanceMethod(class, @selector(onFirstViewRegister));
Method method_EW_onFirstViewRegister = class_getInstanceMethod(self, @selector(EW_onFirstViewRegister));
method_exchangeImplementations(method_onFirstViewRegister, method_EW_onFirstViewRegister);
}
- (void)EW_onFirstViewRegister {
[self EW_onFirstViewRegister];
}
@end
- 为原始类添加交换方法,再做方法替换
#import <objc/runtime.h>
#import <objc/message.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
void EW_onFirstViewRegister(id self, SEL _cmd) {
SEL sel_EW_onFirstViewRegister = sel_registerName("EW_onFirstViewRegister");
((void(*)(id, SEL))objc_msgSend)(self, sel_EW_onFirstViewRegister);
}
+ (void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
SEL sel_onFirstViewRegister = sel_registerName("onFirstViewRegister");
Method method_onFirstViewRegister = class_getInstanceMethod(class, sel_onFirstViewRegister);
SEL sel_EW_onFirstViewRegister = sel_registerName("EW_onFirstViewRegister");
const char *types = method_getTypeEncoding(method_onFirstViewRegister);
BOOL didAddSuccess = class_addMethod(class, sel_EW_onFirstViewRegister, (IMP)EW_onFirstViewRegister, types);
NSLog(@"方法添加:%@", didAddSuccess ? @"成功" : @"失败");
didAddSuccess ? method_exchangeImplementations(method_onFirstViewRegister, class_getInstanceMethod(class, sel_EW_onFirstViewRegister)) : NULL;
}
- 保存原始方法实现,使用新方法覆盖原始方法
//使用OC的方法书写方式
+ (void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
SEL sel_onFirstViewRegister = sel_registerName("onFirstViewRegister");
Method method_onFirstViewRegister = class_getInstanceMethod(class, sel_onFirstViewRegister);
SEL EW_onFirstViewRegister = @selector(EW_onFirstViewRegister);
Method method_EW_onFirstViewRegister = class_getInstanceMethod(self, EW_onFirstViewRegister);
const char *types = method_getTypeEncoding(method_EW_onFirstViewRegister);
IMP imp_EW_onFirstViewRegister = method_getImplementation(method_EW_onFirstViewRegister);
BOOL didAddMethod = class_addMethod(class, EW_onFirstViewRegister, imp_EW_onFirstViewRegister, types);
didAddMethod ? method_exchangeImplementations(method_onFirstViewRegister, class_getInstanceMethod(class, EW_onFirstViewRegister)) : NULL;
}
- (void)EW_onFirstViewRegister {
((void(*)(id, SEL))objc_msgSend)(self, @selector(EW_onFirstViewRegister));
}
//或者使用C语言函数书写的方式
void(*ori_onFirstViewRegister)(id, SEL);
void EW_onFirstViewRegister(id self, SEL _cmd) {
if (ori_onFirstViewRegister) {
ori_onFirstViewRegister(self, _cmd);
}
}
+(void)load {
Class class = objc_getClass("WCAccountLoginControlLogic");
SEL sel_onFirstViewRegister = sel_registerName("onFirstViewRegister");
Method method_onFirstViewRegister = class_getInstanceMethod(class, sel_onFirstViewRegister);
//获取原始方法实现,并进行方法替换
ori_onFirstViewRegister = (void(*)(id, SEL))method_getImplementation(method_onFirstViewRegister);
method_setImplementation(method_onFirstViewRegister, (IMP)EW_onFirstViewRegister);
/*
获取原始方法实现,并进行替换 这两步可以合并在一起
ori_onFirstViewRegister = (void(*)(id, SEL))class_replaceMethod(class, sel_onFirstViewRegister, (IMP)EW_onFirstViewRegister, method_getTypeEncoding(method_EW_onNext));
*/
}