MethodSwizzling是什么
由于OC是一种动态语言,基于消息机制的函数.执行代码的时候,函数并不是在编译阶段就确定了的,是动态绑定的,他们的绑定关系是通过一张映射表来确定的.
与动态绑定对应的是C的静态绑定,会在后文举例说明.
那么既然函数是动态绑定的,我们能不能通过一些方法动态改变这些绑定关系,答案就是MethodSwizzling.它属于C语言,利用的是Runtime机制.
MethodSwizzling怎么用
动态改变这个映射基本分为3个,交换,修改,设置新方法
- 利用 method_exchangeImplementations 来交换2个方法的实现
- 利用 class_replaceMethod 来修改类的实现
- 利用 method_setImplementation 来直接设置某个方法实现
我们以常见的交换举例,先准备两个函数
- (void)oringinalLog {
NSLog(@"Oringinal Method");
}
- (void)swizzlingLog {
NSLog(@"Swizzling Method");
}
函数前后的映射关系如下图
然后我们进行测试
- (void)touchUpInsideButton {
NSLog(@"First Log:");
[self oringinalLog];
[self swizzlingLog];
//Method Swizzling Test
Method oringinal = class_getInstanceMethod([self class], @selector(oringinalLog));
Method swizzling = class_getInstanceMethod([self class], @selector(swizzlingLog));
method_exchangeImplementations(oringinal, swizzling);
NSLog(@"Second Log:");
[self oringinalLog];
[self swizzlingLog];
}
观察输出日志
2016-02-02 10:49:09.664 Learn[1098:619493] First Log:
2016-02-02 10:49:09.665 Learn[1098:619493] Oringinal Method
2016-02-02 10:49:09.665 Learn[1098:619493] Swizzling Method
2016-02-02 10:49:09.665 Learn[1098:619493] Second Log:
2016-02-02 10:49:09.666 Learn[1098:619493] Swizzling Method//替换完成
2016-02-02 10:49:09.666 Learn[1098:619493] Oringinal Method
可以发现第二次打印方法已经被互换,而且一旦执行过Exchange方法后,映射表是一直被交换的,和函数作用域无关,使用另外一个按键测试
- (void)touchUpInsideOtherButton {
NSLog(@"OtherButton Log:");
[self oringinalLog];
[self swizzlingLog];
}
观察日志,可以发现开始没有被替换,后来被替换了
2016-02-02 10:58:11.551 Learn[1149:728289] OtherButton Log:
2016-02-02 10:58:11.551 Learn[1149:728289] Oringinal Method//第一次按没有被替换
2016-02-02 10:58:11.552 Learn[1149:728289] Swizzling Method
2016-02-02 10:58:13.315 Learn[1149:728289] First Log:
2016-02-02 10:58:13.315 Learn[1149:728289] Oringinal Method
2016-02-02 10:58:13.315 Learn[1149:728289] Swizzling Method
2016-02-02 10:58:13.316 Learn[1149:728289] Second Log:
2016-02-02 10:58:13.316 Learn[1149:728289] Swizzling Method
2016-02-02 10:58:13.317 Learn[1149:728289] Oringinal Method//此时Exchange结束
2016-02-02 10:58:14.584 Learn[1149:728289] OtherButton Log:
2016-02-02 10:58:14.584 Learn[1149:728289] Swizzling Method//再次点击,已经被替换
2016-02-02 10:58:14.584 Learn[1149:728289] Oringinal Method
用MethodSwizzling来Hook的递归假象
像刚刚上文那种已经实现了的两个方法,互相交换来使用,很少会遇到这种需求,多数需求还是用自己的函数替代系统的函数,完成一些功能性的插入.
比如我想让每次调用NSString的lowercaseString都把小写化的字符输出一下.由于这个函数是系统的,我们无法进入函数内部修改.
@interface NSString (HookCategory)
- (NSString *)hooked_lowcaseString;
@end
@implementation NSString (HookCategory)
- (NSString *)hooked_lowcaseString {
NSString *lowercase = [self hooked_lowcaseString];
NSLog(@"%@",lowercase);
return lowercase;
}
@end
上文一眼看上去,在hooked_lowcaseString内部调用了hooked_lowcaseString岂不是产生了递归.但是由于是要配合Exchange使用的,并不会产生
我们来看测试代码
NSString *tempStr = @"ABC";
Method oringinal = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swizzling = class_getInstanceMethod([NSString class], @selector(hooked_lowcaseString));
method_exchangeImplementations(oringinal, swizzling);
tempStr = [tempStr lowercaseString];//此处已经调用的是hooked_lowcaseString
进入hooked_lowcaseString函数内部
- (NSString *)hooked_lowcaseString {
//此处调用的是系统lowercaseString,所以不会递归
NSString *lowercase = [self hooked_lowcaseString];
NSLog(@"%@",lowercase);
return lowercase;
}
什么是动态绑定
所谓动态绑定就是和静态绑定对应的,因为OC是C的超集,所以这里通过C来解释,首先定义两个函数.
#import <stdio.h>
void printHello() {
printf("Hello\n");
}
void printGoodbye(){
printf("Goodbye\n");
}
这种就是静态绑定,编译器编译结束就知道这里回执行printHello和printGoodbye函数,所以直接把指针指向了这两个函数的所在地
void doTheThing(int type) {
if(type == 0) {
printHello();
}else {
printGoodbye();
}
return 0;
}
这种就是动态绑定,编译器只是指导这里会声明一个func函数,func函数具体干什么,要等到运行时根据type的值,来让func指向Hello和Goodbye其中一个,所以我们只需要控制type值,就让func有了多种可能性
void doTheThing(int type) {
void (*func)();
if(type == 0) {
func = printHello();
}else {
func = printGoodbye();
}
func();
return 0;
}
OC的动态函数机制
消息调用成功的情况
由于OC是动态绑定机制,所以每个**[类活实例 函数名]**的调用,都是将某个字符串(函数名的Selector)发送到某个地址(可以是类名,也可以是某个实例的内存地址).所以才会有类方法(classMethod)和实例方法(InstanceMethod).
那么对这个字符串(函数名),OC会做哪些处理呢?首先会调用底层的C函数
void objc_msgSend(id self, SEL cmd, ...)
这个函数是个多参数函数,第一个是你传来的地址,第二个就是消息的selector,后边可以附加参数,对于一些特殊情况还有
- objc_msgSend_stret 发送的消息返回一个结构体
- objc_msgSend_fpret 发送的消息返回一个浮点数Float
- objc_msgSendSuper 发送消息给超类就是[super message:parameter]
这是可以正常发送的函数状态,如果遍历了所有的super还没有找对应这个消息的函数,就会抛出异常
这个是由NSObject的**doesNotRecognizeSelector:**抛出的异常
消息调用不成功
消息调用不成功会有三个函数来尝试补救这个问题,三个消息的策略分别是
- 卧槽,这个没见过啊,我看看能不能自己处理掉
- fuck处理不掉啊,算了交给备胎看他行不行
- 备胎说,尼玛,我也搞不定啊,丢回系统找别人吧
动态方法解析
//二选一,尝试在内部捕获这个函数并进行一定的处理
+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveClassMethod:(SEL)selector;
备援接收者
//把这个消息的接收者发送给另外一个对象
//这个对象不是super的一员,因为此时已经遍历了super找不到对应
- (id)forwardingTargetForSelector:(SEL)selector;
完整消息转发
//这里把SEL的target,message的selector字符串本身,parameters重新包装成一个NSInvocation
//然后把这个NSInvocation交还给系统重新进行一次派发(msssage-dispatch system)
- (void)forwardInvocation:(NSInvocation *)invocation;
所以在自定义的Class里的时候,我们可以通过重构这三个函数,来编写对应未知消息的处理方式.
参考文献
[1]http://blog.csdn.net/yiyaaixuexi/article/details/9374411
[2]Effective Objective-C 2.0