Method Swizzle
利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
在OC中,SEL 和 IMP之间的关系,就好像一本书的“目录”。
SEL 是方法编号,就像“标题”一样。
IMP是方法实现的真实地址,就像“页码”一样。
他们是一一对应的关系
Runtime提供了交换两个SEL和IMP对应关系的函数.
通过这个函数交换两个SEL和IMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)
class_addMethod方式:
- 利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃
class_addMethod 参数:
class_addMethod(Class _Nullable __unsafe_unretained cls, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
<#SEL _Nonnull name#>:ClassName
<#IMP _Nonnull imp#> :IMP 方法编号
<#const char * _Nullable types#>):方法类型(返回值+参数):比如"V@:",可通过method_getTypeEncoding(Method)来获得
class_replaceMethod方式:
- 利用class_replaceMethod,直接给原始的方法替换IMP
method_setImplementation方式:
- 利用method_setImplementation,直接重新赋值原始的新的IMP
+(void)load{
//原始的Method
old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
}
IMP (*old_onNext)(id self,SEL _cmd);
//新的IMP
void new_onNext(id self,SEL _cmd)
{
//拿出用户密码!
UITextField * pwd = [[self valueForKey:@"textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"密码是:%@",pwd.text);
old_onNext(self,_cmd);
}
BOOL best_Swizzle(Class aClass, Class bClass,SEL originalSel,SEL swizzleSel){
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(bClass, swizzleSel);
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzleMethod);
}
return YES;
}
example
- 方法交换
+(void)load{
//获取原方法
Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:));
//新方法
Method LHURL = class_getClassMethod(self, @selector(LHURLWithStr:));
//交换
method_exchangeImplementations(URLWithStr, HKURL);
}
+(instancetype)LHURLWithStr:(NSString *)str{
//调用系统原来的方法
NSURL * url = [NSURL LHURLWithStr:str];
if (url == nil) {
str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
}
url = [NSURL HKURLWithStr:str];
return url;
}
调试方法
通过地址对象方法的调用
[(ClassName*)0x999999999 method];
通过对象方法的调用
//从textField中取text
NSString *textString = [[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
###
添加新方法
+(void)load{
//1.拿到原始的Method
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
//2.添加新的方法
BOOL didAdd = class_addMethod(object_getClass(@"ClassName"), @selector(new_onNext), my_next, "V@:");
//3.获取到添加的新方法
Method newOnNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("new_onNext"));
//4.交换
method_exchangeImplementations(onNext, newOnNext);
}
void my_next(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"密码是!%@",pwd);//获取到密码
//调用原来的方法
[self performSelector:@seleceor(new_onNest)];
}
###替换方法
+(void)load{
//1.拿到原始的Method
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
//保存原始IMP
old_onNext = method_getImplementation(onNext);
//3.替换方法
class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), my_next, "v@:");
}
IMP (*old_onNext)(id self,SEL _cmd);
void my_next(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"密码是!%@",pwd);//获取到密码
//调用原来的方法
old_onNext(self,_cmd);
}
### set方法
+(void)load{
//1.拿到原始的Method
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
//保存原始IMP
old_onNext = method_getImplementation(onNext);
//3.set方法
method_setImplementation(onNext, (IMP)my_next);
}
IMP (*old_onNext)(id self,SEL _cmd);
void my_next(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"密码是!%@",pwd);//获取到密码
//调用原来的方法
old_onNext(self,_cmd);
}