iOS Objective-C 消息的轉發

iOS Objective-C 消息的轉發

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

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

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繼續處理
  • 如果是則進入resolveClassMethod繼續處理,并且通過lookUpImpOrNil判斷非空,最后也會調用resolveInstanceMethod進行對象方法的動態決議,因為根據isa走位圖,萬物皆對象,最終都會繼承自NSObject,最后會找到NSObject的對象方法中。

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

/***********************************************************************
* 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));
        }
    }
}

該函數實質是做了一次方法的決議操作

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

舉個例子:

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

實現代碼如下:

+ (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];
}

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

1.3 resolveClassMethod(類方法動態決議)

/***********************************************************************
* 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));
        }
    }
}

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

舉個例子:

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

實現代碼如下:

+ (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];
}

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

+ (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,我們的方法(消息)在原類中也是以對象方法的形式存在的,當調用lookUpImpOrNil時會遞歸查找父類的方法列表,我們無法操作元類以及根元類,因為它們是系統生成的,但是我們可以借助NSObject Category的方式來實現方法的動態決議。如果類實現了方法的動態決議就不會到這里,如果沒實現才會到NSObject的方法動態決議。

2. 消息轉發

2.1 _objc_msgForward_impcache

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

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

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

_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內部實際是調用了_objc_msgForward,緊跟其后的源碼就是__objc_msgForward,下面我們繼續探索

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那樣的有用信息,這里我們并不能發現什么,一時間仿佛線索斷裂,蘋果爸爸只是開源到如此地步,那么我們該如何研究消息轉發的詳細流程呢?回想以前的的步驟找到imp后會繼續進行緩存的填充和日志的打印,在我們的開發過程中往往都會通過日志的打印來發現和解決問題,那么我們不妨看看日志都打印了什么。


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

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

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一下,否則不能調用。調用完畢后我們來到/private/tmp目錄下

日志的存儲路徑

日志結果

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


+ (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);
}

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

控制臺日志

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

2.4 forwardingTargetForSelector(快速轉發流程)

forwardingTargetForSelector

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

舉個例子:

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

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

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

打印結果

根據打印結果我們可以知道LGStudent實例對象發送的saySomething消息最后由LGteacher響應。關于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.)

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

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.

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

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:機械接管之前重定向發送給它的未知消息。 當您只是想將消息重定向到另一個對象時,這是非常有用的,并且可能比常規轉發快一個數量級。如果轉發的目標是捕獲NSInvocation,或者在轉發過程中操縱參數或返回值,那么它就沒有用了。

小結:

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

2.5 methodSignatureForSelector(慢速轉發流程)

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

methodSignatureForSelector

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

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.

譯:該方法用于協議的實現。同時這個方法也用于必須創建NSInvocation對象的情況,比如在消息轉發期間。如果您的對象維護一個委托或能夠處理它沒有直接實現的消息,您應該重寫此方法以返回適當的方法簽名。

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

See Also
forwardInvocation

根據我文檔的定義:在子重寫以將消息轉發給其他對象。

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.)

譯:當向對象發送沒有對應方法的消息時,運行時系統給接收方一個機會將消息委托給另一個接收方。它通過創建一個表示消息的NSInvocation對象并向接收者發送一個包含這個NSInvocation對象作為參數的forwardInvocation:消息來委托消息。然后,接收方的forwardInvocation:方法可以選擇將消息轉發到另一個對象。(如果該對象也不能響應消息,那么它也將獲得一個轉發消息的機會。)

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允許對象與其他對象建立關系,對于某些消息,這些對象將代表它行事。在某種意義上,轉發對象能夠“繼承”它所轉發消息的對象的某些特征。

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.

譯:為了響應對象本身不能識別的方法,您必須重寫methodSignatureForSelector:forwardInvocation:。轉發消息的機制使用methodSignatureForSelector:獲得的信息來創建要轉發的NSInvocation對象。重寫方法必須為給定的選擇器提供適當的方法簽名,可以通過預先構造一個選擇器,也可以通過向另一個對象請求一個選擇器。

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

舉個例子:

還是剛才的工程,注釋掉forwardingTargetForSelector的實現。

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

打印結果如下:

打印結果

可以看到,通過以上代碼的處理saySomething消息也被轉發了。其實當我們注釋了forwardInvocation內部實現,也不會導致崩潰。

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

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

2.6 消息轉發流程圖

從動態方法決議到消息的快速轉發,再到消息的慢速轉發流程如下:


消息轉發流程圖

至此我們的消息轉發流程基本完畢

3. 總結

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


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370