iOS-底層原理10-動(dòng)態(tài)方法決議&消息轉(zhuǎn)發(fā)

《iOS底層原理文章匯總》

上一篇文章《iOS-底層原理09-msgSend消息轉(zhuǎn)發(fā)》中提到哲泊,如果慢速查找在父類的緩存中沒(méi)有找到臼朗,則傳入父類的class,進(jìn)而重新進(jìn)行父類的慢速查找流程...一層層遞歸循環(huán),直到找到方法的Imp后返回。實(shí)質(zhì)上并不是走的這個(gè)遞歸循環(huán),因?yàn)樵诟割惖木彺嬷凶邊R編代碼快速查找找不到之后,并不會(huì)進(jìn)入__objc_msgSend_uncached鲤屡,就不會(huì)走入MethodTableLookup

CacheLookupGETIMP_cache_getImp@2x.png

從父類的緩存中查找的流程imp = cache_getImp(curClass, sel);走到匯編,CacheLookup GETIMP, _cache_getImp福侈,CacheLookup將參數(shù)GETIMP帶入到寄存器p0中酒来,

父類緩存中查找傳入GETIMP.png

當(dāng)父類的緩存中一直沒(méi)有找到此方法時(shí),則進(jìn)入JumpMiss/CheckMiss $0,

GETIMP@2x.png

從而判斷$0是否等于GETIMP,沒(méi)有找到就是GETIMP,進(jìn)入cbz p9, LGetImpMiss,將空值0存入寄存器p0位置肪凛,并直接返回ret = nil堰汉。并沒(méi)有進(jìn)行從匯編開(kāi)始的慢速查找遞歸循環(huán)。

_cache_getImp@2x.png
從父類的緩存中查找返回ret@2x.png

故以上根本沒(méi)有再次進(jìn)入父類的方法慢速查找流程伟墙,遞歸循環(huán)翘鸭,正確的流程圖如下

方法的慢速查找流程---正確.png
  • 方法查找流程:先進(jìn)入本類的快速查找流程->本類的多線程緩存中查找->本類的慢速查找(二分查找)->父類的緩存中查找->父類的慢速查找(二分查找),如果還沒(méi)找到呢?且不做任何處理戳葵。程序?qū)?huì)崩潰報(bào)錯(cuò)就乓。

  • 通過(guò)代碼調(diào)試能夠看到進(jìn)入了父類的實(shí)例方法慢速查找流程,新建LGMankind為L(zhǎng)GPerson的父類拱烁,本類實(shí)例方法慢速查找流程LGPerson->LGMankind->NSObject->nil

27.gif

類中沒(méi)有實(shí)現(xiàn)對(duì)象方法或類方法生蚁,調(diào)用的對(duì)象方法或類方法的時(shí)候會(huì)報(bào)錯(cuò),最常見(jiàn)的錯(cuò)誤戏自,最熟悉的陌生人

unrecognized selector sent to instance 0x10102c040

  • 調(diào)用沒(méi)有實(shí)現(xiàn)的對(duì)象方法
 -[LGPerson say666]: unrecognized selector sent to instance 0x10102c040
2020-11-03 20:25:47.362425+0800 KCObjc[2999:669876] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGPerson say666]: unrecognized selector sent to instance 0x10102c040'

h@2x.png

m@2x.png

main@2x.png

unrecognized.jpg

24.gif
  • 調(diào)用沒(méi)有實(shí)現(xiàn)的類方法
25.gif
類方法未實(shí)現(xiàn).png

沒(méi)有實(shí)現(xiàn)對(duì)象方法或類方法邦投,則會(huì)返回forward_imp,forward_imp = (IMP)_objc_msgForward_impcache; _objc_msgForward_impcache是匯編實(shí)現(xiàn)浦妄,匯編到C++多一個(gè)下劃線_,C++到C少一個(gè)下劃線_尼摹。

// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
forward_imp.jpg
__objc_msgForward_impcache@2x.png
_objc_forward_handler@2x.png
26.gif

沒(méi)有實(shí)現(xiàn)實(shí)例方法或類方法,系統(tǒng)提供一次挽救的機(jī)會(huì)實(shí)現(xiàn):動(dòng)態(tài)方法決議

1.實(shí)例方法的動(dòng)態(tài)方法決議

根據(jù)isa的走位圖剂娄,實(shí)例方法慢速查找流程LGPerson->LGMankind->NSObject->nil

重寫類方法+ (BOOL)resolveInstanceMethod:(SEL)sel蠢涝,給類添加一個(gè)sayMaster的方法,在編譯類加載的時(shí)候,+ (BOOL)resolveInstanceMethod:(SEL)sel就已經(jīng)加載進(jìn)內(nèi)存了阅懦,將sayMaster的實(shí)現(xiàn)Imp寫進(jìn)sel中和二。

resolveInstanceMethod.png
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來(lái)了哦",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);
    }
    
    return [super resolveInstanceMethod:sel];
}

動(dòng)態(tài)方法決議輸出.png

若動(dòng)態(tài)方法決議+ (BOOL)resolveInstanceMethod:(SEL)sel里面,沒(méi)有對(duì)sel方法say666進(jìn)行重新指定imp耳胎,則此動(dòng)態(tài)決議方法+ (BOOL)resolveInstanceMethod:(SEL)sel會(huì)走兩次惯吕,為什么會(huì)走兩次呢?怕午?废登?

resloveInstanceMethod兩次@2x.png

28.gif
  • 第一次動(dòng)態(tài)方法決議后,方法返回郁惜,是什么時(shí)候進(jìn)入第二次動(dòng)態(tài)方法決議方法的呢堡距???通過(guò)打印第二進(jìn)入前的堆棧情況羽戒,獲取堆棧信息如下缤沦,得到第二次觸發(fā)是在CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:],后面探索實(shí)質(zhì)為消息的慢速轉(zhuǎn)發(fā)流程。
第二次動(dòng)態(tài)方法決議進(jìn)入堆棧@2x.png
  • 第二次動(dòng)態(tài)方法決議還沒(méi)找到imp易稠,程序報(bào)錯(cuò)_objc_msgForward_impcache
29.gif
第二次動(dòng)態(tài)方法決議沒(méi)找到imp報(bào)錯(cuò)@2x.png
第二次動(dòng)態(tài)方法決議.png

2.類方法的動(dòng)態(tài)方法決議

根據(jù)isa的走位圖缸废,查找類方法流程元類(metaClass)->根元類(rootMetaClass:NSObject)->根類(NSObject)->nil,根元類繼承于NSObject驶社。

isa流程圖.png
isa走位.png
  • 查找類方法企量,先傳入LGPerson的元類的地址0x0000000100002270,進(jìn)行查找類方法sayNB,獲取類方法衬吆,類方法在元類中是以實(shí)例方法的形式存在的梁钾,類中存在兩個(gè)類方法+ (void)lgClassMethod和+ (BOOL)resolveInstanceMethod:(SEL)sel
類方法@2x.png
  • 1 通過(guò)lldb獲取LGPerson元類中的方法绳泉,相當(dāng)于獲取類方法的個(gè)數(shù)兩個(gè)+ (void)lgClassMethod和+ (BOOL)resolveInstanceMethod:(SEL)sel
lldb獲取類方法@2x.png
  • 方法列表中不存在sayNB的類方法逊抡,此時(shí)繼續(xù)for循環(huán)在LGPerson元類的父類,也就是根元類NSObject中查找sayNB方法零酪,curClass = curClass->superclass冒嫡,curClass的地址為0x00000001003340f0
根元類中查找方法@2x.png
  • 2 通過(guò)lldb查看根元類NSObject中的方法個(gè)數(shù)
根元類NSObject中的方法@2x.png

LGPerson根元類NSObject的方法數(shù)量@2x.png
  • 3 根元類中沒(méi)有找到sayNB方法,繼續(xù)往上查找四苇,在根元類的父類NSObject中繼續(xù)查找sayNB方法
LGPerson根元類NSObject中查找LGPerson的類方法@2x.png
NSObject中的方法@2x.png
NSObject的父類中查找sayNB方法@2x.png
  • 4 根元類的父類中沒(méi)有找到sayNB方法孝凌,進(jìn)入類方法的動(dòng)態(tài)方法決議
  • curClass = curClass->superclass,curClass的地址為0x0000000100334140,沒(méi)有找到進(jìn)入動(dòng)態(tài)方法決議return resolveMethod_locked(inst, sel, cls, behavior);此時(shí)cls傳入的是LGPerson的元類月腋,會(huì)走入resolveClassMethod方法蟀架,能通過(guò)實(shí)現(xiàn)此方法,對(duì)方法進(jìn)行重新添加榆骚,先看一看沒(méi)有實(shí)現(xiàn)此方法的情況下片拍,流程接下來(lái)往哪里走?
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}
  • 沒(méi)有實(shí)現(xiàn)resolveClassMethod:類方法妓肢,此時(shí)lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)一層一層的往上找捌省,LGPerson的元類0x0000000100002270中無(wú)法找到,繼續(xù)往上一層LGPerson的根元類(NSObject)0x00000001003340f0中繼續(xù)查找碉钠,LGPerson的根元類是NSObject的元類纲缓,存放了NSObject的類方法,正好resolveClassMethod:也是NSObject的類方法喊废,所以下面的return語(yǔ)句永遠(yuǎn)都不會(huì)走祝高。

  • 前面如果沒(méi)有實(shí)現(xiàn),后面在NSObject的元類中也就是LGPerson的根元類中一定能找到系統(tǒng)NSObject的類方法resolveClassMethod:的實(shí)現(xiàn)污筷,下面return永遠(yuǎn)不會(huì)走工闺。

if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
}
NSObject的resolveClassMethod@2x.png

流程繼續(xù)往下bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);返回false,因?yàn)閞esolveClassMethod:方法并沒(méi)有在LGPerson中實(shí)現(xiàn),sayNB方法更沒(méi)有添加斤寂。往下執(zhí)行IMP imp = lookUpImpOrNil(inst, sel, cls);查找流程耿焊,仍然找不到sayNB方法。

nonmeta@2x.png
  • 進(jìn)入resolveInstanceMethod(inst, sel, cls);,查看根元類cls->ISA()中是否有實(shí)例方法resolveInstanceMethod:存在遍搞,是有的罗侯,根元類是NSObject的元類,NSObject的類方法resolveInstanceMethod:在NSObjct的元類中以實(shí)例方法存在溪猿,能找到钩杰,不會(huì)走return。 但bool resolved = msg(cls, resolve_sel, sel);為false诊县,因?yàn)長(zhǎng)GPerson的元類中并不存在resolveInstanceMethod:和sayNB讲弄。
類方法進(jìn)入resolveInstanceMethod@2x.png
根元類NSObject中的resolveInstanceMethod方法@2x.png
resolveInstanceMethod@2x.png
類方法sayNB找不到進(jìn)入resolveInstanceMethod@2x.png

若實(shí)現(xiàn)了resolveInstanceMethod:方法,添加了sayNB方法還會(huì)報(bào)錯(cuò)嗎依痊?避除??等到后面分析

  • IMP imp = lookUpImpOrNil(inst, sel, cls);查找sayNB方法胸嘁,找不到返回imp為nil瓶摆,此時(shí),動(dòng)態(tài)方法決議結(jié)束性宏,再次查找一遍sayNB方法群井,確認(rèn)有沒(méi)有添加。return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);,未找到sayNB,返回(IMP) imp = 0x00000001002c262c (libobjc.A.dylib`_objc_msgForward_impcache),報(bào)錯(cuò)unrecognized selector sent to instance 0x10102c040
objc_msgForward_impcache@2x.png
類方法未實(shí)現(xiàn).png

第一次動(dòng)態(tài)方法決議結(jié)束

  • 后面的流程往哪里走呢毫胜?书斜??后面再探索酵使,下面先看下常規(guī)操作荐吉,類方法sayNB找不到,防止報(bào)錯(cuò)的動(dòng)態(tài)方法決議的處理

查詢LGPerson的對(duì)象方法傳入的cls是類LGPerson

LGPerson的對(duì)象方法@2x.png

LGPerson的對(duì)象方法傳入的cls為類LGPerson@2x.png
(lldb) p cls
(Class) $11 = LGPerson
(lldb) p/x cls
(Class) $12 = 0x00000001000032f0 LGPerson

查詢LGPerson的類方法傳入的cls是元類LGPerson

LGPerson的類方法sayNB@2x.png
LGPerson類方法查詢傳入cls為元類LGPerson@2x.png

類方法的動(dòng)態(tài)方法決議

1.重寫resolveClassMethod:方法

sayNB方法找不到凝化,根據(jù)前文分析會(huì)進(jìn)入resolveClassMethod(inst, sel, cls);,此時(shí)的cls為L(zhǎng)GPerson的元類LGPerson,進(jìn)入resolveClassMethod(inst, sel, cls);,查詢LGPerson有沒(méi)有實(shí)現(xiàn)resolveClassMethod:方法稍坯,雖然目前LGPerson中沒(méi)有實(shí)現(xiàn)resolveClassMethod:類方法,但LGPerson元類的父類搓劫,也就是根元類NSObject中瞧哟,存在resolveClassMethod:方法,為什么呢枪向?

因?yàn)橄到y(tǒng)類NSObject的類方法resolveClassMethod:存在NSObject的元類中以實(shí)例方法形式存在勤揩,即根元類NSObject中。

NSObject的resolveClassMethod@2x.png
  • 1.如果啥也不做秘蛔,必定會(huì)報(bào)錯(cuò)陨亡,最熟悉的陌生人
unrecognizedselector@2x.png
  • 2.動(dòng)態(tài)方法決議重寫resolveClassMethod:方法,但不增加sayNB的Imp,動(dòng)態(tài)方法決議中能迅速找到方法resolveClassMethod(inst, sel, cls);
實(shí)現(xiàn)resolveClassMethod方法為添加sayNBImp@2x.png

沒(méi)有添加sayNB的imp,bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);仍然為false

resolveClassMethod@2x.png
未添加sayNB的imp,resolved為false@2x.png

添加sayNB的imp之后傍衡,看resolved的值bool resolved = msg(nonmeta, @selector(resolveClassMethod:),此時(shí)為true

添加sayNB的imp之后resolved為true@2x.png

此時(shí)能找到LGPerson的類方法sayNB:,為什么能找到呢负蠕?蛙埂??

先看編譯時(shí)的遮糖,sayNB方法添加過(guò)程

LGPerson添加sayNB的imp@2x.png
+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ 來(lái)了",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];
}
objc_getMetaClass@2x.png

往元類中添加方法class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
走入m = getMethodNoSuper_nolock(cls, name)绣的,發(fā)現(xiàn)和方法的慢速查找過(guò)程中是用一個(gè)方法,傳入的cls也都是元類欲账,

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}
static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertLocked();

    checkIsKnownClass(cls);
    
    ASSERT(types);
    ASSERT(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {//表示在LGPerson的元類中已經(jīng)存在name
        // already exists
        if (!replace) {//不用傳入的imp進(jìn)行替換
            result = m->imp;
        } else {//用傳入的imp進(jìn)行替換
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        auto rwe = cls->data()->extAllocIfNeeded();

        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        rwe->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}
getMethodNoSuper_nolock@2x.png

傳入cls在LGPerson的元類中進(jìn)行查找屡江,若查找到,是否替換赛不,替換用傳入的imp替換惩嘉,

getMethodNoSuper_nolock查找@2x.png

result = _method_setImplementation(cls, m, imp); 不替換返回原來(lái)的m->imp,

LGPerson元類添加方法imp@2x.png

若沒(méi)找到則在LGPerson的元類cls的data()中進(jìn)行增加內(nèi)存空間,重新添加auto rwe = cls->data()->extAllocIfNeeded();rwe->methods.attachLists(&newlist, 1);實(shí)時(shí)更新類的信息flushCaches(cls);方便下次查找踢故,所以能找到文黎。不會(huì)再報(bào)unrecognized selector sent to instance 0x10102c040找不到方法的錯(cuò)了。

類方法動(dòng)態(tài)方法決議@2x.png

以上是通過(guò)重寫resolveClassMethod:方法畴椰,添加Imp的方式對(duì)類方法進(jìn)行動(dòng)態(tài)方法決議臊诊,類方法還能通過(guò)重寫+ (BOOL)resolveInstanceMethod:(SEL)sel的方式進(jìn)行動(dòng)態(tài)方法決議嗎?斜脂??

類方法的動(dòng)態(tài)方法決議resolveInstanceMethod@2x.png

2.重寫+ (BOOL)resolveInstanceMethod:(SEL)sel

理所當(dāng)然的會(huì)想到重寫+ (BOOL)resolveInstanceMethod:(SEL)sel方法触机,將上面添加元類方法的Imp寫入 (BOOL)resolveInstanceMethod:(SEL)sel中

resolveInstanceMethod中添加LGPerson元類方法@2x.png

發(fā)現(xiàn)添加的方法沒(méi)有生效帚戳,依然報(bào)錯(cuò),為什么會(huì)報(bào)錯(cuò)呢?已經(jīng)往元類中添加過(guò)方法了儡首,為什么沒(méi)有找到呢片任?

2020-11-13 18:10:21.604474+0800 KCObjc[50067:1891886] +[LGPerson sayNB]: unrecognized selector sent to class 0x1000032d8
2020-11-13 18:10:21.607479+0800 KCObjc[50067:1891886] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[LGPerson sayNB]: unrecognized selector sent to class 0x1000032d8'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff374a8b57 __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00000001001318fa objc_exception_throw + 42
    2   CoreFoundation                      0x00007fff37527b37 __CFExceptionProem + 0
    3   CoreFoundation                      0x00007fff3740d3bb ___forwarding___ + 1427
    4   CoreFoundation                      0x00007fff3740cd98 _CF_forwarding_prep_0 + 120
    5   KCObjc                              0x0000000100001a53 main + 67
    6   libdyld.dylib                       0x00007fff71497cc9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
類方法動(dòng)態(tài)方法決議resolveInstanceMethod添加元類LGPerson方法失敗報(bào)錯(cuò)@2x.png

通過(guò)斷點(diǎn)調(diào)試榜跌,查看進(jìn)入resolveInstanceMethod方法中的cls為L(zhǎng)GPerson的元類郎楼,通過(guò)lldb查看LGPerson的元類中存在lgClassMethod和resolveInstanceMethod:兩個(gè)方法磅网,并不存在sayNB方法
這句代碼為falsebool resolved = msg(cls, resolve_sel, sel);

cls為元類@2x.png
LGPerson元類中的方法@2x.png
resolved為false@2x.png

為什么LGPerson的類方法resolveInstanceMethod:中添加的元類方法沒(méi)有生效呢赎离?

因?yàn)長(zhǎng)GPerson的元類是一個(gè)虛擬的類番刊,在代碼中并不存在呵扛,所以在LGPerson的.m文件中增加resolveInstanceMethod:方法,程序并不會(huì)真正進(jìn)入铅搓,就不會(huì)執(zhí)行拗慨,所以sayNB方法就沒(méi)有添加進(jìn)去舞竿,從而報(bào)錯(cuò)京景,此時(shí)沒(méi)有生效的話,可以根據(jù)繼承鏈的關(guān)系LPerson元類->LGPerson根元類->NSObject->nil,NSObject不是一個(gè)虛擬的類骗奖,是一個(gè)實(shí)實(shí)在在的的類确徙,可以新建類別NSObject+LG醒串,將方法寫入到NSObject+LG中,增加resolveInstanceMethod:鄙皇,斷點(diǎn)調(diào)試看是否能生效芜赌。

NSObject中本身存在resolveInstanceMethod:方法,重寫此方法伴逸,返回值保持一致较鼓。return NO。

NSObject中的resolveInstanceMethod@2x.png
NSObjec+LG中增加resolveInstanceMethod@2x.png
resolve為true@2x.png

說(shuō)明NSObject+LG中的resolveInstanceMethod:方法生效违柏,已經(jīng)將sayNB的替代方法lgClassMethod添加到LGPerson的元類中博烂,不會(huì)再報(bào)錯(cuò)。

類方法通過(guò)resolveInstanceMethod動(dòng)態(tài)方法決議@2x.png

結(jié)論:

NSObject+LG是系統(tǒng)的分類漱竖,可能會(huì)多一些系統(tǒng)的方法會(huì)受影響
可以通過(guò)過(guò)濾特定命名規(guī)則的代碼找不到的情況禽篱,做上傳服務(wù)器操作或跳轉(zhuǎn)到首頁(yè)或特定的頁(yè)面,防止程序崩潰

AOP封裝成SDK,一般在這一層不作處理,而進(jìn)行消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)

1.快速轉(zhuǎn)發(fā)流程

instrumentObjcMessageSends輔助分析

在消息慢速轉(zhuǎn)發(fā)流程中IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior),若找到了則進(jìn)入log_and_fill_cache馍惹,滿足條件slowpath(objcMsgLogEnabled && implementer)進(jìn)入logMessageSend躺率,對(duì)方法進(jìn)行打印記錄,詳細(xì)記錄到文件路徑下/tmp/msgSends

lookUpImpOrForward中的log_and_fill_cache@2x.png
log_and_fill_cache方法@2x.png

通過(guò)instrumentObjcMessageSends改變objcMsgLogEnabled的值為true,從而記錄調(diào)用流程万矾,在外部使用內(nèi)部的方法需要添加關(guān)鍵字extern悼吱。extern void instrumentObjcMessageSends(BOOL flag);

instrumentObjcMessageSends@2x.png

在LGPerson中添加一個(gè)沒(méi)有實(shí)現(xiàn)的對(duì)象方法sayHello,查看方法調(diào)用情況,在/tmp/msgSends路徑下會(huì)生成一個(gè)名為msgSends-41550的文件,查看文件中的內(nèi)容即為方法的調(diào)用流程,發(fā)現(xiàn)調(diào)用了- LGPerson NSObject forwardingTargetForSelector:方法

forwardingTargetForSelector官方文檔@2x.png
instrumentObjcMessageSends記錄方法調(diào)用流程@2x.png
msgSends消息記錄@2x.png
forwardingTargetForSelector@2x.png

此時(shí)可以在LGPerson中重寫forwardingTargetForSelector:方法良狈,程序崩潰之前打印了sayHello方法說(shuō)明可以在此方法內(nèi)將有sayHello方法實(shí)現(xiàn)的類返回或在此方法內(nèi)添加sayHello的Imp后添。

forwardingTargetForSelector重寫@2x.png
  • 1.forwardingTargetForSelector:方法中將有sayHello方法實(shí)現(xiàn)的類返回
LGStudent添加對(duì)象方法sayHello@2x.png
消息快速轉(zhuǎn)發(fā)有實(shí)現(xiàn)sayHello對(duì)象方法的類LGStudent@2x.png
// 1: 快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

    // runtime + aSelector + addMethod + imp
    //return [super forwardingTargetForSelector:aSelector];
    return [LGStudent alloc];
}
  • 2.forwardingTargetForSelector:方法中動(dòng)態(tài)添加sayHello的Imp,結(jié)果程序崩潰,為什么呢薪丁?遇西??
快速轉(zhuǎn)發(fā)通過(guò)runtime添加Imp@2x.png

2.慢速轉(zhuǎn)發(fā)流程

在msgSends-41550的文件中發(fā)現(xiàn)在forwardingTargetForSelector:方法之后严嗜,還調(diào)用了- LGPerson NSObject methodSignatureForSelector:方法

methodSignatureForSelector@2x.png
methodSignatureForSelector官方文檔@2x.png

查閱官方文檔- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector和- (void)forwardInvocation:(NSInvocation *)anInvocation要搭配使用粱檀,有兩種方式,一種是重寫方法漫玄,但是不做處理茄蚯,另一種是進(jìn)行事務(wù)的重新賦值

  • 1.重寫- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector和- (void)forwardInvocation:(NSInvocation *)anInvocation方法,不做處理睦优,僅僅防止奔潰
消息慢速轉(zhuǎn)發(fā)@2x.png
  • 2.事務(wù)重新賦值anInvocation.target = [LGStudent alloc];[anInvocation invoke];對(duì)事物進(jìn)行revoke渗常。
事務(wù)invoke@2x.png

大膽做個(gè)假設(shè)可以將事務(wù)進(jìn)行本類方法指定嗎,是可以的

forwardInvocation事務(wù)指向本類中的方法@2x.png

消息轉(zhuǎn)發(fā)流程圖如下

消息轉(zhuǎn)發(fā)機(jī)制.png

以上是以上帝視角探索消息轉(zhuǎn)發(fā)流程刨秆,有沒(méi)有更好的辦法呢凳谦?

反匯編探索消息轉(zhuǎn)發(fā)流程

程序崩潰后通過(guò)bt查看堆棧信息,在程序崩潰之前調(diào)用了CoreFoundation中的forwarding_prep_0forwarding,尋找CoreFoundation的源碼,在官網(wǎng)源碼查找并下載CF-1151.16.tar衡未,在源碼中查找forwarding_prep_0尸执,發(fā)現(xiàn)無(wú)法找到家凯。

CoreFoundation__forwarding_prep_0___@2x.png
CF-1151.16@2x.png
  • 讀取CorFoundation鏡像文件,路徑為/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation,找到CoreFoundation的可執(zhí)行文件
imagelist@2x.png
CoreFoundation可執(zhí)行文件@2x.png
  • 通過(guò)工具Hopper.Demo.dmg查看編譯后的代碼如失,付費(fèi)軟件使用試用版try the demo
CoreFoundation可執(zhí)行文件拖入Hopper@2x.png
Mach-O64bits@2x.png
  • 全局搜索__forwarding_prep_0___,點(diǎn)擊跳轉(zhuǎn)到函數(shù)入口
Hopper__forwarding_prep_0__@2x.png
___forwarding_prep_0__偽代碼模式@2x.png
___forwarding__@2x.png
  • 進(jìn)入__forwarding__流程圖如下绊诲,和前面探索的消息轉(zhuǎn)發(fā)流程不謀而合。
Hopper反編譯消息轉(zhuǎn)發(fā)流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末褪贵,一起剝皮案震驚了整個(gè)濱河市掂之,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脆丁,老刑警劉巖世舰,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異槽卫,居然都是意外死亡跟压,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門歼培,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)震蒋,“玉大人,你說(shuō)我怎么就攤上這事躲庄〔槠剩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵噪窘,是天一觀的道長(zhǎng)笋庄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)效览,這世上最難降的妖魔是什么无切? 我笑而不...
    開(kāi)封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮丐枉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掘托。我一直安慰自己瘦锹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布闪盔。 她就那樣靜靜地躺著弯院,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泪掀。 梳的紋絲不亂的頭發(fā)上听绳,一...
    開(kāi)封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音异赫,去河邊找鬼椅挣。 笑死头岔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鼠证。 我是一名探鬼主播峡竣,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼量九!你這毒婦竟也來(lái)了适掰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荠列,失蹤者是張志新(化名)和其女友劉穎类浪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體肌似,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡费就,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锈嫩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片受楼。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呼寸,靈堂內(nèi)的尸體忽然破棺而出艳汽,到底是詐尸還是另有隱情,我是刑警寧澤对雪,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布河狐,位于F島的核電站,受9級(jí)特大地震影響瑟捣,放射性物質(zhì)發(fā)生泄漏馋艺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一迈套、第九天 我趴在偏房一處隱蔽的房頂上張望捐祠。 院中可真熱鬧,春花似錦桑李、人聲如沸踱蛀。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)率拒。三九已至,卻和暖如春禁荒,著一層夾襖步出監(jiān)牢的瞬間猬膨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工呛伴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勃痴,地道東北人谒所。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像召耘,于是被迫代替她去往敵國(guó)和親百炬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容