Method Swizzle的本質是在運行時交換方法實現(IMP),通常是在原有的方法中,插入本身的業務需求。html
Objective-C的消息機制:在 Objective-C 中調用一個方法, 其實是在底層經過 objc_msgSend()發送一個消息。 而查找消息的惟一依據是selector的方法名。ios
[obj doSomething]; /// => objc_msgSend(obj,@selector(doSomething))
複製代碼
每個OC實例對象都保存有isa指針和實例變量,其中isa指針所屬類,類維護一個運行時可接收的方法列表(MethodLists); 方法列表(MethodLists)中保存selector & IMP的映射關係。在運行時,經過selecter找到匹配的IMP,從而找到的具體的實現函數。git
開發中能夠利用Objective-C的動態特性,在運行時替換selector對應的方法實現(IMP),達到給hook的目的。下圖是利用 Method Swizzle 來替換selector對應IMP後的方法列表示意圖。github
在description() 以前打印「description 被 Swizzle 了」這樣的日誌。編程
@implementation NSObject (Swizzle)
+ (void)load{
//調換IMP
Method originalMethod = class_getInstanceMethod([NSObject class], @selector(description));
Method newMethod = class_getInstanceMethod([NSObject class], @selector(replace_description));
method_exchangeImplementations(originalMethod, newMethod);
}
- (void)replace_description{
NSLog(@"description 被 Swizzle 了");
[self replace_description];
}
@end
複製代碼
使用swizzle時,咱們應該注意哪些問題呢?bash
若是 originalMethod 是其父類實現的,那麼直接 method_exchangeImplementations 是把父類中的 originalMethod 給替換了,致使該父類以及其餘子類調用的 originalMethod 也會被替換函數
解決: 經過 class_addMethod 判斷 method 是否是屬於本類本身實現的?post
代碼:ui
@implementation Model (Swizzle)
+ (void)load {
Class class = [self class];
SEL originalSelector = @selector(hhh);
SEL swizzledSelector = @selector(new_hhh);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 添加 originalSelector->swizzle method 到 class
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) { // 說明originalSelector在父類中
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else { // 說明originalSelector在當前類中
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
複製代碼
若是 originalMethod 中使用了 _cmd參數,可能形成bugspa
@interface IncorrectSwizzleClass : NSObject
- (void) swizzleExample;
- (void) originalMethod;
@end
@implementation IncorrectSwizzleClass
- (void)swizzleExample {
Method m1 = class_getInstanceMethod([self class], @selector(originalMethod));
Method m2 = class_getInstanceMethod([self class], @selector(replaceImp));
method_exchangeImplementations(m1, m2);
}
- (void)originalMethod {
NSLog(@"方法名爲 originalMethod,其 _cmd 的值爲:%@",[NSString stringWithFormat:@"*** -%@", NSStringFromSelector(_cmd)]);
}
- (void)replaceImp {
/*
* 添加本身的邏輯:好比添加log
*/
[self replaceImp];
}
@end
- (void)incorrect {
NSLog(@"#################### incorrect #######################");
IncorrectSwizzleClass* example2 = [[IncorrectSwizzleClass alloc] init];
NSLog(@"## swizzle 以前,調用 originalMethod 的打印信息:");
[example2 originalMethod];
[example2 swizzleExample];
NSLog(@"## swizzle 以後,調用 originalMethod 的打印信息:");
[example2 originalMethod];
}
複製代碼
打印結果:
分析: 執行 OC方法時,默認會傳遞兩個參數(self & _cmd) [self replaceImp]; /// 會被編譯器變成 objc_msgSend(self, @selector(replaceImp)),方法的第二個參數是 @「replaceImp」,故 originalMethod 中打印的是 replaceImp。
解決:C方法+ method_setImplementation 的方式
@interface CorrectSwizzleClass : NSObject
- (void) swizzleExample;
- (void) originalMethod;
@end
static IMP __original_Method_Imp;
void replaceImp(id self, SEL _cmd) {
/*
* 添加本身的邏輯:好比添加 log
*/
((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
}
@implementation CorrectSwizzleClass
- (void)swizzleExample {
Method m = class_getInstanceMethod([self class],@selector(originalMethod));
/// method_setImplementation:return The previous implementation of the method
__original_Method_Imp = method_setImplementation(m,(IMP)replaceImp);
}
- (void)originalMethod {
NSLog(@"方法名爲 originalMethod,其 _cmd 的值爲:%@",[NSString stringWithFormat:@"*** -%@", NSStringFromSelector(_cmd)]);
}
@end
- (void)correct {
NSLog(@"#################### correct #######################");
CorrectSwizzleClass* example = [[CorrectSwizzleClass alloc] init];
NSLog(@"## swizzle 以前,調用 originalMethod 的打印信息:");
[example originalMethod];
[example swizzleExample];
NSLog(@"## swizzle 以後,調用 originalMethod 的打印信息:");
[example originalMethod];
}
複製代碼
打印結果:
只對某個對象進行 swizzle,不影響其餘對象
方案:
Aspects屬於AOP編程的庫,源碼總數不超過1000行,對外就暴露了兩個方法。 使用方式:能夠hook 類方法、對象實例方法,還有三種執行位置:before、insert、after
@interface NSObject (Aspects)
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
@end
複製代碼
例子:
/**
* 事件攔截
* 攔截UIViewController的viewDidLoad方法
*/
[UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo)
{
/**
* 添加咱們要執行的代碼,因爲withOptions 是 AspectPositionAfter。
* 因此每一個控制器的 viewDidLoad 觸發都會執行下面的方法
*/
[self doSomethings];
} error:NULL];
- (void)doSomethings {
//TODO: 好比日誌輸出、統計代碼
NSLog(@"------");
}
複製代碼
_objc_msgForward
_objc_msgForward
是一個函數指針(和 IMP 的類型同樣),是用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward
會直接走消息轉發。看一個不存在方法的例子:
動態生成一個當前對象的子類,並將當前對象與子類關聯,而後替換子類的 forwardInvocation 方法(具體參考源碼)。那麼就能夠將當前對象變成一個子類的實例,同時對於外部使用者而言,仍能夠把它繼續當成原對象使用,並且全部的 swizzle 操做都發生在子類,這樣作的好處是你不須要去更改對象自己的類
參考: