iOS Objective-C 消息的轉(zhuǎn)發(fā)

iOS Objective-C 消息的轉(zhuǎn)發(fā)

1.動態(tài)方法決議(解析)

在上一篇消息查找的文章中我們在消息查找中沒有找到的消息就會進入動態(tài)方法決議代碼中。為了連貫性,本篇中會重新且詳細(xì)的講解一下動態(tài)方法決議。

1.1 resolveMethod_locked

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // 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);
}

resolveMethod_locked主要作用是判斷類是否是元類

  • 如果不是則進入resolveInstanceMethod繼續(xù)處理
  • 如果是則進入resolveClassMethod繼續(xù)處理,并且通過lookUpImpOrNil判斷非空,最后也會調(diào)用resolveInstanceMethod進行對象方法的動態(tài)決議,因為根據(jù)isa走位圖,萬物皆對象,最終都會繼承自NSObject,最后會找到NSObject的對象方法中。

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

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    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 = lookUpImpOrNil(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));
        }
    }
}

該函數(shù)實質(zhì)是做了一次方法的決議操作

  1. 初始化一個selresolveInstanceMethod
  2. 然后查找該sel,找到后則繼續(xù)處理(找到說明實現(xiàn)了該方法),找不到就直接返回
  3. 通過objc_msgSend發(fā)送消息,這里發(fā)送的是resolveInstanceMethod消息,如果返回YES則說明該方法被實現(xiàn),否則未實現(xiàn)。
  4. 如果實現(xiàn)并且決議處做了轉(zhuǎn)發(fā),說明該sel指向了新的imp,并通過下面的打印來說明新IMP被動態(tài)實現(xiàn),或者沒找到。

舉個例子:

聲明一個saySomething的對象方法,但是沒有實現(xiàn),直接調(diào)用肯定會報方法找不到的錯誤,那么上述流程要怎樣處理才能不報錯呢?

實現(xiàn)代碼如下:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"來了老弟:%s - %@",__func__,NSStringFromSelector(sel));

    if (sel == @selector(saySomething)) {
        NSLog(@"說話了");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return [super resolveInstanceMethod:sel];
}

當(dāng)我們調(diào)用saySomething時,因為沒有實現(xiàn)所以找不到該方法,當(dāng)我們實現(xiàn)了resolveInstanceMethod后,并在其內(nèi)部將saySomethingimp指定為我們已經(jīng)實現(xiàn)了的sayHello方法,就不會引起崩潰,最終就會調(diào)用sayHello,這就是runtime給開發(fā)者留下的對于對象方法的一種容錯處理。

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

/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

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

    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;
    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 = lookUpImpOrNil(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 resolveClassMethod:%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));
        }
    }
}

該函數(shù)跟resolveInstanceMethod差不多,唯一的區(qū)別就是發(fā)消息的時候是向元類發(fā)送消息。其余的就不在贅述了。

舉個例子:

跟對象方法的例子一樣首先聲明一個sayLove的類方法,然后沒有實現(xiàn)。調(diào)用后肯定還是會崩潰,這里我們在resolveClassMethod方法中對齊進行處理。

實現(xiàn)代碼如下:

+ (BOOL)resolveClassMethod:(SEL)sel{

    if (sel == @selector(sayLove)) {
        // 類方法在元類 objc_getMetaClass("LGStudent")
        NSLog(@"說- love");
        IMP sayOIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        Method sayOMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        const char *sayOType = method_getTypeEncoding(sayOMethod);
        return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayOIMP, sayOType);
    }
    return [super resolveClassMethod:sel];
}

實現(xiàn)原理跟對象方法的實現(xiàn)也基本差不多當(dāng)我們調(diào)用sayLove時,因為沒有實現(xiàn)所以找不到該方法,當(dāng)我們實現(xiàn)了resolveClassMethod后,并在其內(nèi)部將sayLoveimp指定為我們已經(jīng)實現(xiàn)了的sayObjc方法,就不會引起崩潰,最終就會調(diào)用sayObjc,這就是runtime給開發(fā)者留下的對于類方法的一種容錯處理。這里有一點需要特別注意,就是類方法是存儲在原類中的,無論使我們獲取sayObjc時還是添加新的方法時都應(yīng)該選擇元類進行處理,否則就會找不到方法,從而觸發(fā)resolveInstanceMethod對象方法的動態(tài)決議,如果還是找不到就會崩潰。如果在NSObject中,或者NSObject的分類中實現(xiàn)了resolveInstanceMethod并且使用同樣的放處理sayLove,這時候同樣可以解決由sayLove沒有實現(xiàn)而引起的崩潰。實現(xiàn)代碼如下:(NSObject分類中實現(xiàn))

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayLove)) {
        NSLog(@"說話了");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayEasy));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayEasy));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return NO;
}

為什么可以這樣:
主要原因是resolveMethod_locked中這兩句代碼決定的。上個isa走位圖就會更加清晰。

// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}
isa流程圖

由這個流程圖我們可以知道,元類最終繼承自根元類,根元類又繼承自NSObject,我們的方法(消息)在原類中也是以對象方法的形式存在的,當(dāng)調(diào)用lookUpImpOrNil時會遞歸查找父類的方法列表,我們無法操作元類以及根元類,因為它們是系統(tǒng)生成的,但是我們可以借助NSObject Category的方式來實現(xiàn)方法的動態(tài)決議。如果類實現(xiàn)了方法的動態(tài)決議就不會到這里,如果沒實現(xiàn)才會到NSObject的方法動態(tài)決議。

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

2.1 _objc_msgForward_impcache

如果所有地方均沒有實現(xiàn)方法的動態(tài)決議,那么我們的底層還會有什么處理呢?

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

lookUpImpOrForward方法的一開始我們就初始化了如上代碼所示的imp。當(dāng)找不到方法且沒有實現(xiàn)動態(tài)決議的相關(guān)處理,最后會將此sel_objc_msgForward_impcache進行配對,進入消息的轉(zhuǎn)發(fā)流程,如下圖。

_objc_msgForward_impcache

我們搜索_objc_msgForward_impcache最終又來到objc-msg-arm64.s文件處。代碼如下:

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

2.2 _objc_msgForward

通過源碼我們可以看出__objc_msgForward_impcache內(nèi)部實際是調(diào)用了_objc_msgForward,緊跟其后的源碼就是__objc_msgForward,下面我們繼續(xù)探索

ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward

2.3 通過打印日志尋找流程

看了__objc_msgForward的源碼并沒有什么像objc_msgSend那樣的有用信息,這里我們并不能發(fā)現(xiàn)什么,一時間仿佛線索斷裂,蘋果爸爸只是開源到如此地步,那么我們該如何研究消息轉(zhuǎn)發(fā)的詳細(xì)流程呢?回想以前的的步驟找到imp后會繼續(xù)進行緩存的填充和日志的打印,在我們的開發(fā)過程中往往都會通過日志的打印來發(fā)現(xiàn)和解決問題,那么我們不妨看看日志都打印了什么。


static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);
}



/// logMessageSend
bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char    buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

通過上面的兩個函數(shù)我們可以看到,在objcMsgLogEnabledtrue的時候日志會輸出到/tmp/msgSends-xxx的目錄下。那么該如何讓objcMsgLogEnabledtrue呢,我們先不妨搜索一下,搜完后我們發(fā)現(xiàn)改變objcMsgLogEnabled的值是通過一個名字叫instrumentObjcMessageSends的函數(shù)。

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;
}

那么我們就來試一試,首先要新建一個MacOS工程,然后extern一下,否則不能調(diào)用。調(diào)用完畢后我們來到/private/tmp目錄下

日志的存儲路徑

日志結(jié)果

首先我們就看到了我們熟悉的resolveInstanceMethod,緊接著就是forwardingTargetForSelectormethodSignatureForSelector這兩個方法我們就沒見過了。然后就是doesNotRecognizeSelector,這個方法是打印日志的方法。我們來到objc4-779.1的源碼中搜索這個幾個方法,實現(xiàn)代碼如下:


+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

這時候我們發(fā)現(xiàn)了unrecognized selector sent to instance這就是我們常見的崩潰錯誤的打印實現(xiàn)了。在這個打印完畢后我們在剛才查看日志的工程中的控制臺還看到了如下的日志:

控制臺日志

在控制臺日志中我們看到了CoreFoundation框架中的___forwarding___的調(diào)用,但是我們知道CoreFoundation并沒有開源很多,那么我們先看看官方文檔,先查看一下forwardingTargetForSelectormethodSignatureForSelector

2.4 forwardingTargetForSelector(快速轉(zhuǎn)發(fā)流程)

forwardingTargetForSelector

根據(jù)文檔的釋義,此方法是返回一個能夠定位到未找到消息imp的對象(object),也就是說,這個對象沒有實現(xiàn)該方法,那么就去找另一個對象。

舉個例子:

我們在剛才打印日志的工程中在實現(xiàn)一個LGteacher的類,再其內(nèi)部實現(xiàn)saySomething方法,然后在LGStudent中添加如下代碼:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

其實就是在forwardingTargetForSelector中實現(xiàn)了貍貓換太子的操作,切實應(yīng)用了蘋果官方文檔的解釋,返回了一個實現(xiàn)了該方法對象。打印結(jié)果如下:

打印結(jié)果

根據(jù)打印結(jié)果我們可以知道LGStudent實例對象發(fā)送的saySomething消息最后由LGteacher響應(yīng)。關(guān)于forwardingTargetForSelector蘋果的官方文檔還給出了幾點提示如下:

Discussion(討論)


If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)

譯:如果一個對象實現(xiàn)(或繼承)這個方法,并返回一個非nil(和非self)結(jié)果,那么返回的對象將用作新的接收者對象,消息分派將繼續(xù)到這個新對象。(顯然,如果從這個方法返回self,代碼將陷入無限循環(huán)。) 實際你傳self也不會死循環(huán),在CoreFoundation___forwarding___:方法中我們可以看到在調(diào)用forwardingTargetForSelector后會調(diào)用class_respondsToSelector方法判斷你返回的這個對象是否能夠響應(yīng)該這個sel,如果不可以則會繼續(xù)走消息轉(zhuǎn)發(fā)流程。所以個人覺得蘋果這個文檔就是為了告訴你別這么寫,并不會真的循環(huán)引用。

image.png

If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.

譯:如果你在一個非根類中實現(xiàn)這個方法,并且你的類對于給定的選擇器沒有返回任何東西,那么你應(yīng)該返回父類的實現(xiàn)的結(jié)果。

This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.

譯:此方法讓對象有機會在開銷大得多的forwardInvocation:機械接管之前重定向發(fā)送給它的未知消息。 當(dāng)您只是想將消息重定向到另一個對象時,這是非常有用的,并且可能比常規(guī)轉(zhuǎn)發(fā)快一個數(shù)量級。如果轉(zhuǎn)發(fā)的目標(biāo)是捕獲NSInvocation,或者在轉(zhuǎn)發(fā)過程中操縱參數(shù)或返回值,那么它就沒有用了。

小結(jié):

  1. forwardingTargetForSelector是一個更快的轉(zhuǎn)發(fā)消息的流程,它能直接讓其他可以響應(yīng)的對象來響應(yīng)未知消息。
  2. forwardingTargetForSelector不能反回self不然就會陷入死循環(huán)。(文檔是這么寫的,實際不是)
  3. 在非根類中實現(xiàn)該方法對于給定的選擇器沒有實現(xiàn)任何東西,則需要返回父類的實現(xiàn)也結(jié)果。
  4. forwardingTargetForSelector適用于將消息轉(zhuǎn)發(fā)給其他可以響應(yīng)的該消息的對象,其主要的意思就是返回值和參數(shù)必須都一樣,否則還要進行其他流程。

2.5 methodSignatureForSelector(慢速轉(zhuǎn)發(fā)流程)

我們還是先看看methodSignatureForSelector的官方文檔

methodSignatureForSelector

這里的釋義是methodSignatureForSelector返回一個NSMethodSignature類型的方法簽名對象,該對象包含由給定選擇器標(biāo)識的方法的描述。這里只是個方法簽名,對參數(shù)和返回值沒有要求,這就是在forwardingTargetForSelector小結(jié)里面說的其他流程。

Discussion(討論)


This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.

譯:該方法用于協(xié)議的實現(xiàn)。同時這個方法也用于必須創(chuàng)建NSInvocation對象的情況,比如在消息轉(zhuǎn)發(fā)期間。如果您的對象維護一個委托或能夠處理它沒有直接實現(xiàn)的消息,您應(yīng)該重寫此方法以返回適當(dāng)?shù)姆椒ê灻?/p>

在文檔的末尾處我們還看到有一個叫forwardInvocation的方法,我們點進去看看

See Also
forwardInvocation

根據(jù)我文檔的定義:在子重寫以將消息轉(zhuǎn)發(fā)給其他對象。

Discussion(討論)


When an object is sent a message for which it has no corresponding method, the runtime system gives the receiver an opportunity to delegate the message to another receiver. It delegates the message by creating an NSInvocation object representing the message and sending the receiver a forwardInvocation: message containing this NSInvocation object as the argument. The receiver’s forwardInvocation: method can then choose to forward the message to another object. (If that object can’t respond to the message either, it too will be given a chance to forward it.)

譯:當(dāng)向?qū)ο蟀l(fā)送沒有對應(yīng)方法的消息時,運行時系統(tǒng)給接收方一個機會將消息委托給另一個接收方。它通過創(chuàng)建一個表示消息的NSInvocation對象并向接收者發(fā)送一個包含這個NSInvocation對象作為參數(shù)的forwardInvocation:消息來委托消息。然后,接收方的forwardInvocation:方法可以選擇將消息轉(zhuǎn)發(fā)到另一個對象。(如果該對象也不能響應(yīng)消息,那么它也將獲得一個轉(zhuǎn)發(fā)消息的機會。)

The forwardInvocation: message thus allows an object to establish relationships with other objects that will, for certain messages, act on its behalf. The forwarding object is, in a sense, able to “inherit” some of the characteristics of the object it forwards the message to.

譯:因此,forwardInvocation: message允許對象與其他對象建立關(guān)系,對于某些消息,這些對象將代表它行事。在某種意義上,轉(zhuǎn)發(fā)對象能夠“繼承”它所轉(zhuǎn)發(fā)消息的對象的某些特征。

Important(劃重點)


To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.

譯:為了響應(yīng)對象本身不能識別的方法,您必須重寫methodSignatureForSelector:forwardInvocation:。轉(zhuǎn)發(fā)消息的機制使用methodSignatureForSelector:獲得的信息來創(chuàng)建要轉(zhuǎn)發(fā)的NSInvocation對象。重寫方法必須為給定的選擇器提供適當(dāng)?shù)姆椒ê灻?,可以通過預(yù)先構(gòu)造一個選擇器,也可以通過向另一個對象請求一個選擇器。

顯然methodSignatureForSelectorforwardInvocation是要一起出現(xiàn)的,下面我們通過一個示例來演示如何使用這兩個方法來實現(xiàn)消息的轉(zhuǎn)發(fā)。

舉個例子:

還是剛才的工程,注釋掉forwardingTargetForSelector的實現(xiàn)。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);
    SEL aSelector = [anInvocation selector];

    if ([[LGTeacher alloc] respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:[LGTeacher alloc]];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

打印結(jié)果如下:

打印結(jié)果

可以看到,通過以上代碼的處理saySomething消息也被轉(zhuǎn)發(fā)了。其實當(dāng)我們注釋了forwardInvocation內(nèi)部實現(xiàn),也不會導(dǎo)致崩潰。

(文檔上的其他注意點總結(jié))其他注意點:

  1. forwardInvocation可以查找響應(yīng)anInvocation中的編碼的消息對象,對于所有消息,此對象不必相同
  2. 使用anInvocation將消息發(fā)送到該對象時anInvocation將保存結(jié)果,運行時系統(tǒng)將提取結(jié)果并將其傳遞給原始發(fā)送者
  3. forwardInvocation方法的實現(xiàn)不僅僅可以轉(zhuǎn)發(fā)消息,還可以合并響應(yīng)各種不同消息的代碼,從而避免為每個選擇器編寫單獨方法的麻煩。
  4. forwardInvocation方法對給定消息的響應(yīng)中不僅將其轉(zhuǎn)發(fā)給一個對象,還有可能涉及其他幾個對象
  5. forwardInvocationNSObject的方法,并且只會調(diào)用doesNotRecognizeSelector方法,如果不實現(xiàn)doesNotRecognizeSelector它不會轉(zhuǎn)發(fā)任何消息從而引起異常。

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

從動態(tài)方法決議到消息的快速轉(zhuǎn)發(fā),再到消息的慢速轉(zhuǎn)發(fā)流程如下:


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

至此我們的消息轉(zhuǎn)發(fā)流程基本完畢

3. 總結(jié)

  1. 動態(tài)方法決議有對象方法動態(tài)解析resolveInstanceMethod和類方法動態(tài)解析resolveClassMethod兩種,都需要開發(fā)者去實現(xiàn)
  2. 消息轉(zhuǎn)發(fā)同樣分為快速消息轉(zhuǎn)發(fā)forwardingTargetForSelector和慢速消息轉(zhuǎn)發(fā)methodSignatureForSelector
  3. 慢速消息轉(zhuǎn)發(fā)同時還需要開發(fā)者實現(xiàn)forwardInvocation方法
  4. 快速消息轉(zhuǎn)發(fā)是讓其他能響應(yīng)的對象來響應(yīng)未查找到的消息,對參數(shù)和返回值要求絕對匹配
  5. 慢速消息轉(zhuǎn)發(fā)提供了更加細(xì)粒度的控制,首先會返回一個方法簽名給runtime,然后通過anInvocation保存結(jié)果,Runtime會提取結(jié)果并將其傳遞給原始發(fā)送者


至此我們的消息或者說方法,在Objective-C的底層實現(xiàn)由objc_msgSend開始,探索了消息發(fā)送的流程,然后由消息找不到時的處理進入到了動態(tài)方法決議,然后通過_objc_msgForward_impcache進入到消息的轉(zhuǎn)發(fā)流程就結(jié)束了,探索過程比較粗糙,也會有些瑕疵,如有問題歡迎指正。。

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

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