動(dòng)態(tài)方法決議
首先我們?cè)?code>objc_msgSend的快速
和慢速查找
后都沒有找到對(duì)應(yīng)的方法,這時(shí)候我們就會(huì)去調(diào)用resolveMethod_locked
膘茎,這是蘋果給提供的一次機(jī)會(huì)(重新查詢一遍)薪夕。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 動(dòng)態(tài)方法決議 : 給一次機(jī)會(huì) 重新查詢
if (! cls->isMetaClass()) { // 對(duì)象 - 類
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else { // 類方法 - 元類
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
通過上述代碼可知脚草,肯定會(huì)調(diào)用resolveInstanceMethod
或者resolveClassMethod
,區(qū)別就是類方法
與實(shí)例方法
的的不同原献,因此我們通過重寫
這兩個(gè)方法去檢測(cè)崩潰
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來了",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
通過運(yùn)行我們發(fā)現(xiàn)馏慨,雖然程序
依然崩潰
,但是我們檢測(cè)到
了say666
方法姑隅,因此可以肯定必定會(huì)走此方法写隶,接下來我們就可以在里面處理邏輯
,避免崩潰讲仰。
- (void)sayMaster{
NSLog(@"%s",__func__);
}
if (sel == @selector(say666)) {
NSLog(@"%@ 來了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
首先我們實(shí)現(xiàn)一個(gè)方法sayMaster
慕趴,獲取sayMaster
的imp
與sel
,當(dāng)我們檢測(cè)到say666
方法時(shí)就可以更改為sayMaster
方法的實(shí)現(xiàn)鄙陡,這樣就可以避免指定方法沒有實(shí)現(xiàn)
導(dǎo)致的崩潰冕房。
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ 來了",NSStringFromSelector(sel));
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
同理,我們也可以攔截類方法
進(jìn)行更改指定方法的實(shí)現(xiàn)來避免崩潰
思考:
通過在resolveInstanceMethod
方法中實(shí)現(xiàn)對(duì)sayNB
方法的更改我們發(fā)現(xiàn)還是崩潰了趁矾,我們之前了解到既然類方法
在元類
中存儲(chǔ)的時(shí)候也是實(shí)例方法
耙册,那我們?yōu)槭裁床荒茉诋?dāng)前類直接更改resolveInstanceMethod
來實(shí)現(xiàn)呢?
解析:
我們?cè)诋?dāng)前類中實(shí)現(xiàn)resolveInstanceMethod
時(shí)毫捣,它只是針對(duì)當(dāng)前類
的實(shí)例方法
的檢測(cè)详拙,因此我們檢測(cè)不到類方法帝际,故而我們猜測(cè)因?yàn)?code>類方法是存在元類
中,我們需要通過給NSObjcet
增加分類
方法來實(shí)現(xiàn)resolveInstanceMethod
就可以進(jìn)行更改:
#import <objc/message.h>
@implementation NSObject (LG)
// 調(diào)用方法的時(shí)候 - 分類
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
通過增加
分類
方法我們發(fā)現(xiàn)確實(shí)可以通過resolveInstanceMethod
來實(shí)現(xiàn)類方法
的檢測(cè)
饶辙,但是如果在此更改的話會(huì)如上圖所示攔截到許多額外的方法
,這樣就可以隱藏更多的問題蹲诀。
消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)也是分為快速轉(zhuǎn)發(fā)
與慢速轉(zhuǎn)發(fā)
兩種情況,首先我們根據(jù)日志追蹤
去查找其中的流程
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
我們?cè)?code>lookUpImpOrForward中發(fā)現(xiàn)查找的過程會(huì)通過log_and_fill_cache
進(jìn)行日志存儲(chǔ)
弃揽,我們通過查找其中存儲(chǔ)的路徑
去查看對(duì)應(yīng)的日志:
我們通過查找其位置
/private/tmp/msgSend-XXX
去獲取日志文件通過日志內(nèi)容侧甫,我們可以清晰的看到其中的方法調(diào)用
forwardingTargetForSelector
(快速流程)與methodSignatureForSelector
(慢速流程)
快速流程
因此我們可以通過重寫這兩個(gè)方法去攔截對(duì)應(yīng)的內(nèi)容
通過蘋果官方文檔可以發(fā)現(xiàn)
forwardingTargetForSelector
的方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [LGStudent alloc];
}
重新創(chuàng)建一個(gè)類
或在其他類
中實(shí)現(xiàn)對(duì)應(yīng)的方法
,當(dāng)該方法
有實(shí)現(xiàn)
(即能查找到方法
的imp
)時(shí)就不會(huì)崩潰蹋宦。
慢速流程
根據(jù)蘋果官方文檔我們可以得出
methodSignatureForSelector
需要與forwardInvocation
進(jìn)行配合使用披粟。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//返回方法簽名,方法簽名可以通過官網(wǎng)type Encoding 進(jìn)行查看
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
// GM sayHello - anInvocation - 漂流瓶 - anInvocation
anInvocation.target = [LGStudent alloc];
// anInvocation 保存 - 方法
[anInvocation invoke];
}
反編譯
上面的方法我們是通過官網(wǎng)
查找進(jìn)行更改的,如果發(fā)現(xiàn)不了對(duì)應(yīng)的方法
我們可以通過反編譯
的方法進(jìn)行跟蹤冷冗,主要工具是hopper
以及IDA
守屉,我們通過介紹hopper
的方法去進(jìn)行查看。
首先我們通過打印堆棧(bt)
發(fā)現(xiàn)崩潰點(diǎn)在CoreFoundation
中
然后通過查看
鏡像
位置 image list
獲取CoreFoundation
的存儲(chǔ)位置/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
接下來我們需要找到對(duì)應(yīng)的文件
并將執(zhí)行文件
添加到hopper
中由上面的崩潰信息可知主要是
_forwarding_prep_0
與_forwarding_
蒿辙,通過全局查找我們可以看到然后點(diǎn)擊
_forwarding_
去查看實(shí)現(xiàn)代碼拇泛,通過查找我們發(fā)現(xiàn)重點(diǎn)為這就可以尋找到消息的
快速轉(zhuǎn)發(fā)
,值得注意的是:
loc_64fb7:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
}
goto loc_6501c;
loc_6501c:
rax = sel_getName(var_140);
r14 = rax;
rax = sel_getUid(rax);
if (rax != var_140) {
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
}
rbx = @selector(doesNotRecognizeSelector:);
if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
____forwarding___.cold.2(var_138);
}
rax = _objc_msgSend(var_138, rbx);
asm{ ud2 };
return rax;
如果查找不到就會(huì)返回doesNotRecognizeSelector