Objective-C 動態(tài)方法決議

上篇文章分析了 消息慢速查找 流程,當(dāng)消息找不到的時候會執(zhí)行_objc_msgForward_impcache匯編代碼。最終調(diào)用到_objc_forward_handler進(jìn)行報錯處理,那么在報錯之前能夠進(jìn)行處理么?

一、動態(tài)方法決議

當(dāng)imp沒有找到的時候的時候會賦值libobjc.A.dylib_objc_msgForward_impcache`,首先會進(jìn)入如下代碼邏輯:

  if (slowpath(behavior & LOOKUP_RESOLVER)) {
      behavior ^= LOOKUP_RESOLVER;
      //要查找的對象,方法,類,1
      return resolveMethod_locked(inst, sel, cls, behavior);
  }
  • 這其實(shí)可以理解為一個單類,相同流程只會進(jìn)入一次。
  • behavior上篇文章已經(jīng)分析,值中有LOOKUP_INITIALIZE|LOOKUP_RESOLVER進(jìn)入后異或LOOKUP_INITIALIZE|LOOKUP_RESOLVER^ LOOKUP_RESOLVER = LOOKUP_INITIALIZE,相當(dāng)于清空了LOOKUP_RESOLVER
  • resolveMethod_locked參數(shù)最后一個是LOOKUP_INITIALIZE。

resolveMethod_locked的源碼如下:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        //這里的cls是類
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            //這里的cls是元類
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    //又會去查找一次,既然這里又會去查找一次,那么肯定有什么地方會加入之前查找不存在的方法。
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  • 當(dāng)快速和慢速消息查找都沒有找到的時候進(jìn)入了resolveMethod_locked
  • 查找的是實(shí)例方法則進(jìn)行對象方法動態(tài)決議resolveInstanceMethod。
  • 查找的是類方法則先進(jìn)行類方法動態(tài)決議resolveClassMethod,再執(zhí)行resolveInstanceMethod(這里resolveInstanceMethod調(diào)用與實(shí)例方法的resolveInstanceMethod參數(shù)不同。)。
  • 最后會調(diào)用lookUpImpOrForwardTryCache查找。

核心問題是最后要返回imp,那么先看下lookUpImpOrForwardTryCache進(jìn)行的操作:

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}

只是一個簡單的調(diào)用,繼續(xù)排查:

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    //是否初始化,正常情況下是已經(jīng)初始化了。
    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        //這就是慢速消息查找流程,與之前的區(qū)別是 behavior = LOOKUP_INITIALIZE,沒有動態(tài)方法決議參數(shù)了。
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    //緩存查找
    IMP imp = cache_getImp(cls, sel);
    //找到直接跳轉(zhuǎn)done
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    //動態(tài)共享緩存查找
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    //imp不存在繼續(xù)慢速消息查找流程
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    //是否消息轉(zhuǎn)發(fā)
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    //返回imp
    return imp;
}
  • isInitialized正常情況是不會進(jìn)入的。
  • 先去緩存查找對應(yīng)的imp,找到直接返回。
  • 沒有找到會去動態(tài)共享緩存查找(如果支持)。
  • 仍然沒有會進(jìn)行lookUpImpOrForward也就是再進(jìn)行一次慢速消息查找。

既然這個函數(shù)也是進(jìn)行快速和慢速消息查找的,那么就說明resolveInstanceMethodresolveClassMethod可以在某個時機(jī)將方法加入類中。這樣后面方法的調(diào)用才有意義。

二、對象方法動態(tài)決議 resolveInstanceMethod

通過源碼分析發(fā)現(xiàn)在進(jìn)行了快速與慢速消息查找后如果找不到imp,蘋果仍然給了機(jī)會進(jìn)行resolveInstanceMethod處理,那么核心肯定是要給類中添加imp,源碼如下:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    //先進(jìn)行元類查找是否實(shí)現(xiàn)了`resolveInstanceMethod`實(shí)例方法,也就是類的類方法。沒有實(shí)現(xiàn)直接返回,這里不會返回,因?yàn)镹Sobject默認(rèn)實(shí)現(xiàn)了。
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //系統(tǒng)自動發(fā)送了`resolveInstanceMethod`消息,由于消息的接受者是類,所以是+方法。
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    //快速慢速查找
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 先進(jìn)行元類resolveInstanceMethod的查找和緩存。
  • 系統(tǒng)自動給類發(fā)送了resolveInstanceMethod消息。既然是類調(diào)用的,那么就是+方法。
  • 接著進(jìn)行了快速慢速方法查找imp,但是沒有返回imp(為什么不返回?這里只是緩存,如果有的話)。
  • lookUpImpOrNilTryCachelookUpImpOrForwardTryCache唯一的區(qū)別是是否進(jìn)行動態(tài)轉(zhuǎn)發(fā)。這里不進(jìn)行動態(tài)轉(zhuǎn)發(fā)。
  • 可以看到返回的resolved只是進(jìn)行了日志打印。也就是resolved返回YES/NO對功能沒有影響。

那么就有個問題?
既然查找了imp為什么不進(jìn)行返回操作?而resolveInstanceMethod調(diào)用結(jié)束后還查了一次?

2.1 + (BOOL)resolveInstanceMethod 調(diào)試分析

resolveInstanceMethod源碼跟蹤流程如下:

  • cls->ISA元類也就是HPObject元類中查找有沒有實(shí)現(xiàn)resolveInstanceMethod-imp,最終會找到NSObject元類然后將resolveInstanceMethod-imp緩存寫入HPObject元類的緩存。(NSObject默認(rèn)實(shí)現(xiàn)了)。
  • 給類發(fā)送resolveInstanceMethod消息。
  • HPObject查找instanceMethod有沒有實(shí)現(xiàn),沒有實(shí)現(xiàn)會將instanceMethod-_objc_msgForward_impcache(IMP)寫入HPObject緩存。這個時候由于LOOKUP_NIL的存在返回的是`nil。
  • 如果lookUpImpOrNilTryCache沒有找到imp會返回繼續(xù)執(zhí)行lookUpImpOrForwardTryCache繼續(xù)進(jìn)行緩存->消息慢速查找流程(消息慢速查找不會執(zhí)行)。因?yàn)榍懊嬉呀?jīng)寫入了對應(yīng)緩存。這次會從緩存中獲取到imp_objc_msgForward_impcacheimp。不會進(jìn)入消息慢速查找流程,直接進(jìn)行了消息轉(zhuǎn)發(fā)。

這就說明resolveInstanceMethod中首先元類查找resolveInstanceMethod,目的是將resolveInstanceMethod寫入緩存。然后類發(fā)送resolveInstanceMethod消息。接著lookUpImpOrNilTryCache調(diào)用是將的imp加入緩存中(無論是否找到,找不到會存入_objc_msgForward_impcache)。返回后lookUpImpOrForwardTryCache從緩存中找方法返回。

結(jié)論:resolveInstanceMethod中l(wèi)ookUpImpOrNilTryCache只是將方法插入緩存,返回后lookUpImpOrForwardTryCache從緩存中獲取imp 這也是調(diào)用兩次的原因。

2.2 + (BOOL)resolveInstanceMethod 實(shí)現(xiàn)

既然系統(tǒng)已經(jīng)給了+ (BOOL)resolveInstanceMethod:(SEL)sel進(jìn)行容錯處理,那么就實(shí)現(xiàn)下:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

調(diào)用后發(fā)現(xiàn)這個方法調(diào)用了兩次:

resolveInstanceMethod: HPObject-instanceMethod
resolveInstanceMethod: HPObject-instanceMethod
  • HPObject的元類中能找到resolveInstanceMethod方法,緩存的直接是自己的imp了。
  • 仍然是沒有命中進(jìn)行了消息轉(zhuǎn)發(fā)。

消息轉(zhuǎn)發(fā)會進(jìn)入class_getInstanceMethod

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

又進(jìn)行了一次lookUpImpOrForward所以這也是調(diào)用了兩次的原因。但是這次不進(jìn)行消息轉(zhuǎn)發(fā)了,所以不會造成死循環(huán)。

總結(jié):第一次沒有命中后,再進(jìn)行消息轉(zhuǎn)發(fā)后又會進(jìn)行一次lookUpImpOrForward消息慢速查找流程,所以resolveInstanceMethod會執(zhí)行兩次。

那么如果實(shí)現(xiàn)中添加了imp就肯定只調(diào)用一次了。
修改代碼如下:

- (void)instanceMethod1 {
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"HPObject resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
    if (sel == @selector(instanceMethod)) {
        IMP instanceMethod1 = class_getMethodImplementation(self, @selector(instanceMethod1));
        Method method = class_getInstanceMethod(self, @selector(instanceMethod1));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, instanceMethod1, type);
    }
    return NO;
}

按照源碼理解在lookUpImpOrNilTryCache調(diào)用中只是增加到了緩存中,后面lookUpImpOrForwardTryCache會從緩存中查找,找到imp然后執(zhí)行。

+ (BOOL)resolveInstanceMethod:(SEL)sel返回NO/YES根據(jù)源碼來看只是打印日志相關(guān)的內(nèi)容,應(yīng)該是沒有影響的。經(jīng)過調(diào)試驗(yàn)證確實(shí)沒有影響。

結(jié)論:

  • resolveInstanceMethod 調(diào)用中只是對方法的緩存,lookUpImpOrNilTryCache 從緩存中再次查找方法。這也是為什么會查找兩次的原因。
  • resolveInstanceMethod 執(zhí)行兩次的原因是,在方法沒有命中的時候消息轉(zhuǎn)發(fā)過程中會再次進(jìn)行l(wèi)ookUpImpOrForward(消息慢速查找),這就是執(zhí)行兩次的原因。
  • + (BOOL)resolveInstanceMethod:(SEL)sel 返回值不會影響功能,只是對日志打印有影響,并且默認(rèn)情況下是不打印日志的。

三、類方法動態(tài)決議resolveClassMethod

在上面最開始分析的時候類方法動態(tài)決議會先調(diào)用resolveClassMethod,如果沒有命中那么就會調(diào)用resolveInstanceMethod

resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}

resolveClassMethod的實(shí)現(xiàn)如下:

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    //不會進(jìn)入這里,先查找元類是否實(shí)現(xiàn)`resolveClassMethod`
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    //類方法存在元類中,操作元類防止沒有實(shí)現(xiàn)。
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //非元類調(diào)用,也就是類方法
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {//......}
}
  • 第一個lookUpImpOrNilTryCache查找元類是否實(shí)現(xiàn),先將resolveClassMethod插入HPObject元類的緩存中。resolveClassMethodNSObject默認(rèn)實(shí)現(xiàn)了。
  • 操作元類,防止元類沒有實(shí)現(xiàn)。
  • 元類中是以對象方法存在,所以在類中實(shí)現(xiàn)類方法就可以了。系統(tǒng)主動給類方法發(fā)送+ resolveClassMethod消息。這里細(xì)節(jié)的一點(diǎn)是通過nonmeta來發(fā)送消息。
  • lookUpImpOrNilTryCache查找目標(biāo)imp,先緩存后慢速。查找到后將imp插入緩存,沒有找到則將_objc_msgForward_impcache插入緩存。

實(shí)現(xiàn)如下:

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

調(diào)用后發(fā)現(xiàn)打印了8次:

resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
  • 其中encodeWithOSLogCoder與我們無關(guān),classMethod出現(xiàn)兩次符合預(yù)期(另外一次消息轉(zhuǎn)發(fā)過程中調(diào)用)。
  • 當(dāng)調(diào)用resolveClassMethod沒有實(shí)現(xiàn)的時候,就調(diào)用resolveInstanceMethod去查找(這里的cls參數(shù)是元類,與查找實(shí)例方法不同),仍然沒有找到就執(zhí)行lookUpImpOrForwardTryCache。
  • 最后在消息轉(zhuǎn)發(fā)的時候會再執(zhí)行一次方法動態(tài)決議。

修改實(shí)現(xiàn):

+ (void)classMethod1 {
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
    if (sel == @selector(classMethod)) {
        IMP classMethod1 = class_getMethodImplementation(objc_getMetaClass("HPObject"), @selector(classMethod1));
        Method method = class_getClassMethod(self, @selector(classMethod1));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("HPObject"), sel, classMethod1, type);
    }
    return [super resolveClassMethod:sel];
}

輸出:

resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
+[HPObject classMethod1]

這個時候就調(diào)用一次了。

既然resolveClassMethod找不到的時候會執(zhí)行一次resolveInstanceMethod,那意味者可以在resolveInstanceMethod中對類方法進(jìn)行處理。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"HPObject resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
    return NO;
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"HPObject resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

這個時候調(diào)試發(fā)現(xiàn)resolveInstanceMethod并沒有執(zhí)行。為什么?因?yàn)檫@里是HPObject元類調(diào)用resolveInstanceMethod。

根據(jù)isa的走位圖,NSObject同時也是元類,那么元類調(diào)用+方法就要存到元類的元類中也就是存在根元類的元類,那么就是NSObject自己,通過NSObjectresolveInstanceMethod方法就可以實(shí)現(xiàn)了。
添加一個NSObject的分類,實(shí)現(xiàn)方法:

- (void)instanceMethod1 {
    NSLog(@"%s",__func__);
}

+ (void)classMethod1 {
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@-%p-%@",self,self,NSStringFromSelector(sel));
    if (sel == @selector(instanceMethod)) {
        IMP instanceMethod1 = class_getMethodImplementation(self, @selector(instanceMethod1));
        Method method = class_getInstanceMethod(self, @selector(instanceMethod1));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, instanceMethod1, type);
    } else if (sel == @selector(classMethod)) {
        IMP classMethod1 = class_getMethodImplementation(objc_getMetaClass("HPObject"), @selector(classMethod1));
        Method method = class_getInstanceMethod(objc_getMetaClass("HPObject"), @selector(classMethod1));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("HPObject"), sel, classMethod1, type);
    }
    return NO;
}

分別調(diào)用instanceMethodclassMethod輸出如下:

HPObject:0x1000082c8, HPMetaObject:0x1000082a0, NSObject:0x100358140, NSMetaObject:0x1003580f0

resolveInstanceMethod: HPObject-0x1000082c8-instanceMethod //類
-[NSObject(Additions) instanceMethod1]
resolveInstanceMethod: HPObject-0x1000082a0-classMethod //元類
HPObjcTest[59242:11857560] +[NSObject(Additions) classMethod1]

這樣就在NSObjectresolveInstanceMethod中即處理了類方法也處理了實(shí)例方法。兩次調(diào)用參數(shù)不同,一次是類調(diào)用,一次是元類調(diào)用。

??如果兩個都實(shí)現(xiàn)在HPObject類中,則都是類調(diào)用。

總結(jié)

  • resolveClassMethod 調(diào)用中只是對方法的緩存,lookUpImpOrNilTryCache會從緩存中再次查找方法,這也是為什么會查找兩次的原因。
  • resolveClassMethod 執(zhí)行兩次的原因是在方法沒有命中的時候消息轉(zhuǎn)發(fā)過程中會再次進(jìn)行l(wèi)ookUpImpOrForward(消息慢速查找),再次走這個流程。這就是執(zhí)行兩次的原因。LOOKUP_NIL有值,所以不會再次消息轉(zhuǎn)發(fā),不會造成死循環(huán)。)
  • resolveClassMethod 沒有命中的時候會先調(diào)用resolveInstanceMethod(這里的cls是元類),再次調(diào)用時因?yàn)?code>NSObject是元類的父類。
  • 這里resolveInstanceMethod由于是元類調(diào)用,所以只能實(shí)現(xiàn)在NSObject的分類中。(根元類的元類是自己,它的父類是NSObject
  • + (BOOL)resolveClassMethod:(SEL)sel 返回值不會影響功能,只是對日志打印有影響。

三、aop & oop

那么動態(tài)方法決議的意義在哪里呢?
這是蘋果在sel查找imp找不到的時候給的一次解決錯誤的機(jī)會。有什么意義呢?在NSObject的分類中,所有找不到的OC方法都能在resolveInstanceMethod中監(jiān)聽到。
那么在自己的工程中可以根據(jù)類名前綴、模塊以及事物進(jìn)行區(qū)分prefix_ module_traffic。當(dāng)發(fā)現(xiàn)有問題的時候可以進(jìn)行容錯處理并且上報錯誤信息。 比如HP_Setting_didClickLogin出現(xiàn)問題的時候進(jìn)行上報,當(dāng)超過閾值時進(jìn)行報警。

這種方式就是aop切面編程。我們比較習(xí)慣的方式是oop。

oop
oop分工非常明確,耦合度小,冗余代碼。一般情況下會提取公共的類,但是遵循后會對它有強(qiáng)依賴,強(qiáng)耦合。
這些其實(shí)不是我們關(guān)心的,我們更關(guān)心業(yè)務(wù)的內(nèi)容,所以公共類盡量少侵入,最好無侵入。通過動態(tài)方式注入代碼,對原始方法沒有影響。這就相當(dāng)于整個切面切入了,要切入的方法和類就是切點(diǎn)。aopoop的延伸。

aop
aop的缺點(diǎn)在上面的例子中是if-else過多冗余。正如上面看到的那樣,方法會調(diào)用很多次浪費(fèi)了相應(yīng)的性能。如果命中還好,沒有命中會走多次,會有性能消耗。它是消息轉(zhuǎn)發(fā)機(jī)制的前一個階段。意味著如果在這里做了容錯處理,后面的流程就被切掉了。蘋果寫轉(zhuǎn)發(fā)流程就沒有意義了。

如果其它模塊也做了相應(yīng)處理,重復(fù)了這塊不一定會執(zhí)行到。所以在后面的流程做aop更合理。

四、消息轉(zhuǎn)發(fā)流程

如果最終動態(tài)方法決議也沒有找到imp呢?動態(tài)方法決議會返回imp,這個時候的imp是指向_objc_msgForward_impcache的。

那么這個時候后面的流程怎么執(zhí)行呢?

可以通過聲明一個函數(shù)instrumentObjcMessageSends打印系統(tǒng)調(diào)用的方法的列表,調(diào)用和聲明方式如下:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HPObject *obj = [HPObject alloc];
        instrumentObjcMessageSends(YES);
        [obj instanceMethod];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

在源碼中它的實(shí)現(xiàn)如下:

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

作用是什么呢?
搜索objcMsgLogEnabled會發(fā)現(xiàn)在開啟的情況下會在/tmp/msgSends-%d中寫下日志:

image.png

調(diào)用輸出結(jié)果如下:
image.png

可以看到調(diào)用了非常多的方法,其中resolveInstanceMethod已經(jīng)是熟悉的了。其它的是消息轉(zhuǎn)發(fā)流程的方法了。這里很遺憾的是看不到參數(shù)。

  • 根據(jù)日志可以看到在methodSignatureForSelector后再次進(jìn)行了resolveInstanceMethod。
  • 根據(jù)源碼分析可知,應(yīng)該是有兩個方法被調(diào)用。
  • 在動態(tài)方法決議后消息轉(zhuǎn)發(fā)流程包含方法:
forwardingTargetForSelector:
methodSignatureForSelector:
doesNotRecognizeSelector:

那么在源碼中調(diào)用跟蹤下參數(shù)呢?
既然都是調(diào)用的NSObject的方法不防在NSObject里面打斷點(diǎn),根據(jù)之前的調(diào)試也能判斷出來應(yīng)該是

encodeWithOSLogCoder:options:maxLength

驗(yàn)證確實(shí)是:


image.png

消息轉(zhuǎn)發(fā)整個流程將在下篇文章詳細(xì)分析。

動態(tài)方法決議整個流程圖:


動態(tài)方法決議流程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容