1 method swizzle方式的选择
1.1 错误的swizzle方式
根据 right-way-to-swizzle 文章的阐述,当我们进行方法交换时,实质是交换了objc_method
结构体中的IMP
函数指针
struct objc_method
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
例如:我们hook一个方法method,这里叫 originalMethodName
,通过method_exchangeImplementations
交换其实现IMP
未swizzle前,method的内容如下
Method m1 { //this is the original method. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
Method m2 { //this is the swizzle method. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
进行method_exchangeImplementations
交换objc_method中的IMP
m1 = class_getInstanceMethod([MyClass class], @selector(originalMethodName));
m2 = class_getInstanceMethod([MyClass class], @selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2)
swizzle method之后,method的内容如下:
Method m1 { //this is the original Method struct. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
可以看出SEL
和IMP
并不匹配,.m文件
我们一般会这样写
.m
我们一般会这样写
- (void)originalMethodName {
// do your logic
}
- (void)swizzle_originalMethodName {
// hook before action
[self swizzle_originalMethodName]; //调用原有的originalMethodName方法
// hook after action
}
即调用swizzle_originalMethodName
来触发原方法的原因是SEL
和IMP
并不对应,当方法实现中,使用_cmd
来获取方法名时,就获取的SEL
和预计的不一致,为了解决这样的问题,就在方法交换时,不破坏objc_method
结构的含义,SEL和IMP对应。
_cmd
在Objective-C的方法中表示当前方法(objc_method)的selector思考:那么使用
__FUNCTION__
获取函数名是否就可以了呢?对viewDidLoad方法swizzle后同一方法中两个输出打印为:
__FUNCTION__: -[CCRootViewController viewDidLoad_swizzle] SEL:viewDidLoad
__FUNCTION__是可以判断函数名,而不是取SEL
- (void) originalMethodName //m1
{
assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]); //方法交换后,这里断言就会拦截掉,不会往下走了,因为_cmd并不是originalMethodNamed,而是swizzle_originalMethodName
//method_exchangedImplementations()
//…
1.2 正确的swizzle方式
避免使用Objective-C声明方法的方式,因为其会创建一个objc_method结构体,我们仅仅需要替换其IMP
,那就创建一个C函数(用于替换IMP)
void __Swizzle_OriginalMethodName(id self, SEL _cmd)
{
//code
}
IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;
使用method_setImplementation(method, swizzleImp);
方法来设定其objc_method
中的IMP
,其返回值会返回原始的IMP
IMP originalImp = method_setImplementation(method,swizzleImp);
原始的IMP我们可以通过函数指针来存储,这样在swizzleImp
实现中,我们调用这个函数指针,既可以达到了hook的目的。
1.3 案例
原文中的用例贴出:
SwizzleExampleClass.h
@interface SwizzleExampleClass : NSObject
- (void) swizzleExample;
- (int) originalMethod;
@end
SwizzleExampleClass.m
static IMP __original_Method_Imp;
int _replacement_Method(id self, SEL _cmd)
{
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
//code
int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
return returnValue + 1;
}
@implementation SwizzleExampleClass
- (void) swizzleExample //call me to swizzle
{
Method m = class_getInstanceMethod([self class],
@selector(originalMethod));
__original_Method_Imp = method_setImplementation(m,
(IMP)_replacement_Method);
}
- (int) originalMethod
{
//code
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
return 1;
}
@end