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));
}
}
}
該函數實質是做了一次方法的決議操作
- 初始化一個sel
resolveInstanceMethod
- 然后查找該sel,找到后則繼續處理(找到說明實現了該方法),找不到就直接返回
- 通過
objc_msgSend
發送消息,這里發送的是resolveInstanceMethod
消息,如果返回YES
則說明該方法被實現,否則未實現。 - 如果實現并且決議處做了轉發,說明該
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
后,并在其內部將saySomething
的imp
指定為我們已經實現了的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
后,并在其內部將sayLove
的imp
指定為我們已經實現了的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);
}
由這個流程圖我們可以知道,元類最終繼承自根元類,根元類又繼承自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-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;
}
通過上面的兩個函數我們可以看到,在objcMsgLogEnabled
為true
的時候日志會輸出到/tmp/msgSends-xxx
的目錄下。那么該如何讓objcMsgLogEnabled
為true
呢,我們先不妨搜索一下,搜完后我們發現改變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
,緊接著就是forwardingTargetForSelector
和methodSignatureForSelector
這兩個方法我們就沒見過了。然后就是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
并沒有開源很多,那么我們先看看官方文檔,先查看一下forwardingTargetForSelector
和methodSignatureForSelector
2.4 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
,或者在轉發過程中操縱參數或返回值,那么它就沒有用了。
小結:
-
forwardingTargetForSelector
是一個更快的轉發消息的流程,它能直接讓其他可以響應的對象來響應未知消息。 -
forwardingTargetForSelector
不能反回self
不然就會陷入死循環。(文檔是這么寫的,實際不是) - 在非根類中實現該方法對于給定的選擇器沒有實現任何東西,則需要返回父類的實現也結果。
-
forwardingTargetForSelector
適用于將消息轉發給其他可以響應的該消息的對象,其主要的意思就是返回值和參數必須都一樣,否則還要進行其他流程。
2.5 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
的方法,我們點進去看看
根據我文檔的定義:在子重寫以將消息轉發給其他對象。
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
對象。重寫方法必須為給定的選擇器提供適當的方法簽名,可以通過預先構造一個選擇器,也可以通過向另一個對象請求一個選擇器。
顯然methodSignatureForSelector
和 forwardInvocation
是要一起出現的,下面我們通過一個示例來演示如何使用這兩個方法來實現消息的轉發。
舉個例子:
還是剛才的工程,注釋掉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
內部實現,也不會導致崩潰。
(文檔上的其他注意點總結)其他注意點:
-
forwardInvocation
可以查找響應anInvocation
中的編碼的消息對象,對于所有消息,此對象不必相同 - 使用
anInvocation
將消息發送到該對象時anInvocation
將保存結果,運行時系統將提取結果并將其傳遞給原始發送者 -
forwardInvocation
方法的實現不僅僅可以轉發消息,還可以合并響應各種不同消息的代碼,從而避免為每個選擇器編寫單獨方法的麻煩。 -
forwardInvocation
方法對給定消息的響應中不僅將其轉發給一個對象,還有可能涉及其他幾個對象 -
forwardInvocation
是NSObject
的方法,并且只會調用doesNotRecognizeSelector
方法,如果不實現doesNotRecognizeSelector
它不會轉發任何消息從而引起異常。
2.6 消息轉發流程圖
從動態方法決議到消息的快速轉發,再到消息的慢速轉發流程如下:
至此我們的消息轉發流程基本完畢
3. 總結
- 動態方法決議有對象方法動態解析
resolveInstanceMethod
和類方法動態解析resolveClassMethod
兩種,都需要開發者去實現 - 消息轉發同樣分為快速消息轉發
forwardingTargetForSelector
和慢速消息轉發methodSignatureForSelector
- 慢速消息轉發同時還需要開發者實現
forwardInvocation
方法 - 快速消息轉發是讓其他能響應的對象來響應未查找到的消息,對參數和返回值要求絕對匹配
- 慢速消息轉發提供了更加細粒度的控制,首先會返回一個方法簽名給
runtime
,然后通過anInvocation
保存結果,Runtime
會提取結果并將其傳遞給原始發送者
至此我們的消息或者說方法,在Objective-C
的底層實現由objc_msgSend
開始,探索了消息發送的流程,然后由消息找不到時的處理進入到了動態方法決議,然后通過_objc_msgForward_impcache
進入到消息的轉發流程就結束了,探索過程比較粗糙,也會有些瑕疵,如有問題歡迎指正。。