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