今天在看ReactiveCocoa的rac_signalForSelector源碼時,很好奇它們是怎么做到的??吹剿鋵嵑虯spects、jspatch的hook原理其實是一樣的。好吧。。。走起。
oc中的Method實際是這樣一個結(jié)構(gòu)。
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
SEL是方法名,IMP其實就是一個C函數(shù)指針,可以直接強制轉(zhuǎn)換的。runtime 提供了一些api,比如method_getImplementation,可以直接操作這些函數(shù)。
方法調(diào)用流程
在 Objective-C 中,所有的 [receiver message] 都會轉(zhuǎn)換為 objc_msgSend(receiver, @selector(message));
尋找 IMP 的過程(查找過程):
- 在當(dāng)前 class 的方法緩存里尋找(cache methodLists)
找到了跳到對應(yīng)的方法實現(xiàn),沒找到繼續(xù)往下執(zhí)行 - 從當(dāng)前 class 的 方法列表里查找(methodLists),找到了添加到緩存列表里,然后跳轉(zhuǎn)到對應(yīng)的方法實現(xiàn);沒找到繼續(xù)往下執(zhí)行
- 從 superClass 的緩存列表和方法列表里查找,直到找到基類為止
以上步驟還找不到 IMP,則用_objc_msgForward函數(shù)指針代替 IMP 。最后,執(zhí)行這個 IMP 。
這里是關(guān)鍵點。
貼個偽代碼:
// objc-runtime-new.mm 文件里與 _objc_msgForward 有關(guān)的三個函數(shù)使用偽代碼展示
// Created by https://github.com/ChenYilong
// Copyright (c) 微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
// 同時,這也是 obj_msgSend 的實現(xiàn)過程
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //調(diào)用這個函數(shù),偽代碼...
}
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息轉(zhuǎn)發(fā)
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { //先查緩存,緩存沒有時重建,仍舊沒有則向父類查詢
if (!curClass) break;
if (!curClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curClass, sel);
if (imp) break;
} while (curClass = curClass->superclass);
return imp;
}
下面是消息轉(zhuǎn)發(fā)的流程(轉(zhuǎn)發(fā)過程):
- 第一個接手的具體方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,當(dāng)方法是實例方法時調(diào)用前者,當(dāng)方法為類方法時,調(diào)用后者。這個方法設(shè)計的目的是為了給類利用 class_addMethod 添加方法的機會。(這不是重點)
- 第二個階段是備援接收者階段,對象的具體方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此時,運行時詢問能否把消息轉(zhuǎn)給其他接收者處理,也就是此時系統(tǒng)給了個將這個 SEL 轉(zhuǎn)給其他對象的機會。(這也不是重點)
- 第三個階段是完整消息轉(zhuǎn)發(fā)階段,對應(yīng)方法-(void)forwardInvocation:(NSInvocation *)anInvocation,這是消息轉(zhuǎn)發(fā)流程的最后一個環(huán)節(jié)。(這個是重點,hook方案的核心集中在這里)
用圖表示如下:

aa.png
- 如果這里還處理不了。那只能拋出沒有找到方法的異常了
=================================================
流程講完了,我們開始探討hook到底是怎么實現(xiàn)的。第二次進(jìn)入正題。
hook大體分兩步:
- 直接替換原方法的實現(xiàn)為_objc_msgForward。當(dāng)原來的函數(shù)被調(diào)用時,就不會在類方法,父類方法列表里查找實現(xiàn)了,直接表示找不到,進(jìn)入轉(zhuǎn)發(fā)流程。代碼如下:
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
用_objc_msgForward來代替原來的函數(shù)。
- 替換forwardInvocation:的實現(xiàn),當(dāng)進(jìn)入轉(zhuǎn)發(fā)流程時,階段一和階段二都不接手,在階段三forwardInvocation:里會接手。
代碼如下:
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
//hook時要添加的代碼放在這里
if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
}
};
class_replaceMethod(class, @selector(forwardInvocation:), imp_implementationWithBlock(newForwardInvocation), "v@:@");
這里會替換forwardInvocation:的實現(xiàn)。用newForwardInvocation代替。這樣就可以hook啦,完成自己的邏輯后,還要調(diào)用被hook的函數(shù)原來的邏輯。