iOS Objective-C 消息的轉(zhuǎn)發(fā)
1.動(dòng)態(tài)方法決議(解析)
在上一篇消息查找的文章中我們?cè)谙⒉檎抑袥](méi)有找到的消息就會(huì)進(jìn)入動(dòng)態(tài)方法決議代碼中。為了連貫性,本篇中會(huì)重新且詳細(xì)的講解一下動(dòng)態(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
主要作用是判斷類是否是元類
- 如果不是則進(jìn)入
resolveInstanceMethod
繼續(xù)處理 - 如果是則進(jìn)入
resolveClassMethod
繼續(xù)處理混狠,并且通過(guò)lookUpImpOrNil
判斷非空忌愚,最后也會(huì)調(diào)用resolveInstanceMethod
進(jìn)行對(duì)象方法的動(dòng)態(tài)決議柴底,因?yàn)楦鶕?jù)isa
走位圖,萬(wàn)物皆對(duì)象讯屈,最終都會(huì)繼承自NSObject
绑蔫,最后會(huì)找到NSObject
的對(duì)象方法中运沦。
1.2 resolveInstanceMethod(對(duì)象方法動(dòng)態(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ù)實(shí)質(zhì)是做了一次方法的決議操作
- 初始化一個(gè)sel
resolveInstanceMethod
- 然后查找該sel,找到后則繼續(xù)處理(找到說(shuō)明實(shí)現(xiàn)了該方法)配深,找不到就直接返回
- 通過(guò)
objc_msgSend
發(fā)送消息携添,這里發(fā)送的是resolveInstanceMethod
消息,如果返回YES
則說(shuō)明該方法被實(shí)現(xiàn)篓叶,否則未實(shí)現(xiàn)烈掠。 - 如果實(shí)現(xiàn)并且決議處做了轉(zhuǎn)發(fā)羞秤,說(shuō)明該
sel
指向了新的imp
,并通過(guò)下面的打印來(lái)說(shuō)明新IMP
被動(dòng)態(tài)實(shí)現(xiàn)左敌,或者沒(méi)找到瘾蛋。
舉個(gè)例子:
聲明一個(gè)saySomething
的對(duì)象方法,但是沒(méi)有實(shí)現(xiàn)矫限,直接調(diào)用肯定會(huì)報(bào)方法找不到的錯(cuò)誤哺哼,那么上述流程要怎樣處理才能不報(bào)錯(cuò)呢?
實(shí)現(xiàn)代碼如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"來(lái)了老弟:%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(saySomething)) {
NSLog(@"說(shuō)話了");
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
時(shí)叼风,因?yàn)闆](méi)有實(shí)現(xiàn)所以找不到該方法取董,當(dāng)我們實(shí)現(xiàn)了resolveInstanceMethod
后,并在其內(nèi)部將saySomething
的imp
指定為我們已經(jīng)實(shí)現(xiàn)了的sayHello
方法咬扇,就不會(huì)引起崩潰甲葬,最終就會(huì)調(diào)用sayHello
廊勃,這就是runtime
給開(kāi)發(fā)者留下的對(duì)于對(duì)象方法的一種容錯(cuò)處理懈贺。
1.3 resolveClassMethod(類方法動(dòng)態(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ā)消息的時(shí)候是向元類發(fā)送消息坡垫。其余的就不在贅述了梭灿。
舉個(gè)例子:
跟對(duì)象方法的例子一樣首先聲明一個(gè)sayLove
的類方法,然后沒(méi)有實(shí)現(xiàn)冰悠。調(diào)用后肯定還是會(huì)崩潰堡妒,這里我們?cè)?code>resolveClassMethod方法中對(duì)齊進(jìn)行處理。
實(shí)現(xiàn)代碼如下:
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayLove)) {
// 類方法在元類 objc_getMetaClass("LGStudent")
NSLog(@"說(shuō)- 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];
}
實(shí)現(xiàn)原理跟對(duì)象方法的實(shí)現(xiàn)也基本差不多當(dāng)我們調(diào)用sayLove
時(shí)溉卓,因?yàn)闆](méi)有實(shí)現(xiàn)所以找不到該方法皮迟,當(dāng)我們實(shí)現(xiàn)了resolveClassMethod
后,并在其內(nèi)部將sayLove
的imp
指定為我們已經(jīng)實(shí)現(xiàn)了的sayObjc
方法桑寨,就不會(huì)引起崩潰伏尼,最終就會(huì)調(diào)用sayObjc
,這就是runtime
給開(kāi)發(fā)者留下的對(duì)于類方法的一種容錯(cuò)處理尉尾。這里有一點(diǎn)需要特別注意爆阶,就是類方法是存儲(chǔ)在原類中的,無(wú)論使我們獲取sayObjc
時(shí)還是添加新的方法時(shí)都應(yīng)該選擇元類進(jìn)行處理沙咏,否則就會(huì)找不到方法辨图,從而觸發(fā)resolveInstanceMethod
對(duì)象方法的動(dòng)態(tài)決議,如果還是找不到就會(huì)崩潰肢藐。如果在NSObject
中故河,或者NSObject
的分類中實(shí)現(xiàn)了resolveInstanceMethod
并且使用同樣的放處理sayLove
,這時(shí)候同樣可以解決由sayLove
沒(méi)有實(shí)現(xiàn)而引起的崩潰吆豹。實(shí)現(xiàn)代碼如下:(NSObject
分類中實(shí)現(xiàn))
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sayLove)) {
NSLog(@"說(shuō)話了");
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
中這兩句代碼決定的鱼的。上個(gè)isa
走位圖就會(huì)更加清晰杉女。
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
由這個(gè)流程圖我們可以知道,元類最終繼承自根元類鸳吸,根元類又繼承自NSObject
熏挎,我們的方法(消息)在原類中也是以對(duì)象方法的形式存在的,當(dāng)調(diào)用lookUpImpOrNil
時(shí)會(huì)遞歸查找父類的方法列表晌砾,我們無(wú)法操作元類以及根元類坎拐,因?yàn)樗鼈兪窍到y(tǒng)生成的,但是我們可以借助NSObject Category
的方式來(lái)實(shí)現(xiàn)方法的動(dòng)態(tài)決議养匈。如果類實(shí)現(xiàn)了方法的動(dòng)態(tài)決議就不會(huì)到這里哼勇,如果沒(méi)實(shí)現(xiàn)才會(huì)到NSObject
的方法動(dòng)態(tài)決議。
2. 消息轉(zhuǎn)發(fā)
2.1 _objc_msgForward_impcache
如果所有地方均沒(méi)有實(shí)現(xiàn)方法的動(dòng)態(tài)決議呕乎,那么我們的底層還會(huì)有什么處理呢积担?
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
在lookUpImpOrForward
方法的一開(kāi)始我們就初始化了如上代碼所示的imp
。當(dāng)找不到方法且沒(méi)有實(shí)現(xiàn)動(dòng)態(tài)決議的相關(guān)處理猬仁,最后會(huì)將此sel
與_objc_msgForward_impcache
進(jìn)行配對(duì)帝璧,進(jìn)入消息的轉(zhuǎn)發(fā)流程,如下圖湿刽。
我們搜索_objc_msgForward_impcache
最終又來(lái)到objc-msg-arm64.s
文件處的烁。代碼如下:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
2.2 _objc_msgForward
通過(guò)源碼我們可以看出__objc_msgForward_impcache
內(nèi)部實(shí)際是調(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 通過(guò)打印日志尋找流程
看了__objc_msgForward
的源碼并沒(méi)有什么像objc_msgSend
那樣的有用信息渴庆,這里我們并不能發(fā)現(xiàn)什么,一時(shí)間仿佛線索斷裂雅镊,蘋果爸爸只是開(kāi)源到如此地步襟雷,那么我們?cè)撊绾窝芯肯⑥D(zhuǎn)發(fā)的詳細(xì)流程呢?回想以前的的步驟找到imp
后會(huì)繼續(xù)進(jìn)行緩存的填充和日志的打印仁烹,在我們的開(kāi)發(fā)過(guò)程中往往都會(huì)通過(guò)日志的打印來(lái)發(fā)現(xiàn)和解決問(wè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;
}
通過(guò)上面的兩個(gè)函數(shù)我們可以看到晃危,在objcMsgLogEnabled
為true
的時(shí)候日志會(huì)輸出到/tmp/msgSends-xxx
的目錄下叙赚。那么該如何讓objcMsgLogEnabled
為true
呢,我們先不妨搜索一下僚饭,搜完后我們發(fā)現(xiàn)改變objcMsgLogEnabled
的值是通過(guò)一個(gè)名字叫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;
}
那么我們就來(lái)試一試,首先要新建一個(gè)MacOS工程鳍鸵,然后extern
一下苇瓣,否則不能調(diào)用。調(diào)用完畢后我們來(lái)到/private/tmp
目錄下
首先我們就看到了我們熟悉的resolveInstanceMethod
偿乖,緊接著就是forwardingTargetForSelector
和methodSignatureForSelector
這兩個(gè)方法我們就沒(méi)見(jiàn)過(guò)了击罪。然后就是doesNotRecognizeSelector
哲嘲,這個(gè)方法是打印日志的方法。我們來(lái)到objc4-779.1
的源碼中搜索這個(gè)幾個(gè)方法媳禁,實(shí)現(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);
}
這時(shí)候我們發(fā)現(xiàn)了unrecognized selector sent to instance
這就是我們常見(jiàn)的崩潰錯(cuò)誤的打印實(shí)現(xiàn)了眠副。在這個(gè)打印完畢后我們?cè)趧偛挪榭慈罩镜墓こ讨械目刂婆_(tái)還看到了如下的日志:
在控制臺(tái)日志中我們看到了CoreFoundation
框架中的___forwarding___
的調(diào)用,但是我們知道CoreFoundation
并沒(méi)有開(kāi)源很多竣稽,那么我們先看看官方文檔囱怕,先查看一下forwardingTargetForSelector
和methodSignatureForSelector
2.4 forwardingTargetForSelector(快速轉(zhuǎn)發(fā)流程)
根據(jù)文檔的釋義,此方法是返回一個(gè)能夠定位到未找到消息imp
的對(duì)象(object)毫别,也就是說(shuō)娃弓,這個(gè)對(duì)象沒(méi)有實(shí)現(xiàn)該方法,那么就去找另一個(gè)對(duì)象岛宦。
舉個(gè)例子:
我們?cè)趧偛糯蛴∪罩镜墓こ讨性趯?shí)現(xiàn)一個(gè)LGteacher
的類台丛,再其內(nèi)部實(shí)現(xiàn)saySomething
方法,然后在LGStudent
中添加如下代碼:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
其實(shí)就是在forwardingTargetForSelector
中實(shí)現(xiàn)了貍貓換太子的操作砾肺,切實(shí)應(yīng)用了蘋果官方文檔的解釋挽霉,返回了一個(gè)實(shí)現(xiàn)了該方法對(duì)象。打印結(jié)果如下:
根據(jù)打印結(jié)果我們可以知道LGStudent
實(shí)例對(duì)象發(fā)送的saySomething
消息最后由LGteacher
響應(yīng)债沮。關(guān)于forwardingTargetForSelector
蘋果的官方文檔還給出了幾點(diǎn)提示如下:
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.)
譯:如果一個(gè)對(duì)象實(shí)現(xiàn)(或繼承)這個(gè)方法炼吴,并返回一個(gè)非
nil
(和非self
)結(jié)果,那么返回的對(duì)象將用作新的接收者對(duì)象疫衩,消息分派將繼續(xù)到這個(gè)新對(duì)象。(顯然荣德,如果從這個(gè)方法返回self
闷煤,代碼將陷入無(wú)限循環(huán)。) 實(shí)際你傳self也不會(huì)死循環(huán)涮瞻,在CoreFoundation___forwarding___:
方法中我們可以看到在調(diào)用forwardingTargetForSelector
后會(huì)調(diào)用class_respondsToSelector
方法判斷你返回的這個(gè)對(duì)象是否能夠響應(yīng)該這個(gè)sel鲤拿,如果不可以則會(huì)繼續(xù)走消息轉(zhuǎn)發(fā)流程。所以個(gè)人覺(jué)得蘋果這個(gè)文檔就是為了告訴你別這么寫(xiě)署咽,并不會(huì)真的循環(huán)引用近顷。
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.
譯:如果你在一個(gè)非根類中實(shí)現(xiàn)這個(gè)方法,并且你的類對(duì)于給定的選擇器沒(méi)有返回任何東西宁否,那么你應(yīng)該返回父類的實(shí)現(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.
譯:此方法讓對(duì)象有機(jī)會(huì)在開(kāi)銷大得多的
forwardInvocation:
機(jī)械接管之前重定向發(fā)送給它的未知消息。 當(dāng)您只是想將消息重定向到另一個(gè)對(duì)象時(shí)慕匠,這是非常有用的饱须,并且可能比常規(guī)轉(zhuǎn)發(fā)快一個(gè)數(shù)量級(jí)。如果轉(zhuǎn)發(fā)的目標(biāo)是捕獲NSInvocation
台谊,或者在轉(zhuǎn)發(fā)過(guò)程中操縱參數(shù)或返回值蓉媳,那么它就沒(méi)有用了譬挚。
小結(jié):
-
forwardingTargetForSelector
是一個(gè)更快的轉(zhuǎn)發(fā)消息的流程,它能直接讓其他可以響應(yīng)的對(duì)象來(lái)響應(yīng)未知消息酪呻。 -
forwardingTargetForSelector
不能反回self
不然就會(huì)陷入死循環(huán)减宣。(文檔是這么寫(xiě)的,實(shí)際不是) - 在非根類中實(shí)現(xiàn)該方法對(duì)于給定的選擇器沒(méi)有實(shí)現(xiàn)任何東西玩荠,則需要返回父類的實(shí)現(xiàn)也結(jié)果蚪腋。
-
forwardingTargetForSelector
適用于將消息轉(zhuǎn)發(fā)給其他可以響應(yīng)的該消息的對(duì)象,其主要的意思就是返回值和參數(shù)必須都一樣姨蟋,否則還要進(jìn)行其他流程屉凯。
2.5 methodSignatureForSelector(慢速轉(zhuǎn)發(fā)流程)
我們還是先看看methodSignatureForSelector
的官方文檔
這里的釋義是methodSignatureForSelector
返回一個(gè)NSMethodSignature
類型的方法簽名對(duì)象,該對(duì)象包含由給定選擇器標(biāo)識(shí)的方法的描述眼溶。這里只是個(gè)方法簽名悠砚,對(duì)參數(shù)和返回值沒(méi)有要求,這就是在forwardingTargetForSelector
小結(jié)里面說(shuō)的其他流程堂飞。
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é)議的實(shí)現(xiàn)灌旧。同時(shí)這個(gè)方法也用于必須創(chuàng)建
NSInvocation
對(duì)象的情況,比如在消息轉(zhuǎn)發(fā)期間绰筛。如果您的對(duì)象維護(hù)一個(gè)委托或能夠處理它沒(méi)有直接實(shí)現(xiàn)的消息枢泰,您應(yīng)該重寫(xiě)此方法以返回適當(dāng)?shù)姆椒ê灻?/p>
在文檔的末尾處我們還看到有一個(gè)叫forwardInvocation
的方法,我們點(diǎn)進(jìn)去看看
根據(jù)我文檔的定義:在子重寫(xiě)以將消息轉(zhuǎn)發(fā)給其他對(duì)象铝噩。
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ā)送沒(méi)有對(duì)應(yīng)方法的消息時(shí)衡蚂,運(yùn)行時(shí)系統(tǒng)給接收方一個(gè)機(jī)會(huì)將消息委托給另一個(gè)接收方。它通過(guò)創(chuàng)建一個(gè)表示消息的
NSInvocation
對(duì)象并向接收者發(fā)送一個(gè)包含這個(gè)NSInvocation
對(duì)象作為參數(shù)的forwardInvocation:
消息來(lái)委托消息骏庸。然后毛甲,接收方的forwardInvocation:
方法可以選擇將消息轉(zhuǎn)發(fā)到另一個(gè)對(duì)象。(如果該對(duì)象也不能響應(yīng)消息具被,那么它也將獲得一個(gè)轉(zhuǎn)發(fā)消息的機(jī)會(huì)玻募。)
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
允許對(duì)象與其他對(duì)象建立關(guān)系一姿,對(duì)于某些消息七咧,這些對(duì)象將代表它行事。在某種意義上叮叹,轉(zhuǎn)發(fā)對(duì)象能夠“繼承”它所轉(zhuǎn)發(fā)消息的對(duì)象的某些特征艾栋。
Important(劃重點(diǎn))
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)對(duì)象本身不能識(shí)別的方法,您必須重寫(xiě)
methodSignatureForSelector:
和forwardInvocation:
衬横。轉(zhuǎn)發(fā)消息的機(jī)制使用methodSignatureForSelector:
獲得的信息來(lái)創(chuàng)建要轉(zhuǎn)發(fā)的NSInvocation
對(duì)象裹粤。重寫(xiě)方法必須為給定的選擇器提供適當(dāng)?shù)姆椒ê灻梢酝ㄟ^(guò)預(yù)先構(gòu)造一個(gè)選擇器,也可以通過(guò)向另一個(gè)對(duì)象請(qǐng)求一個(gè)選擇器遥诉。
顯然methodSignatureForSelector
和 forwardInvocation
是要一起出現(xiàn)的拇泣,下面我們通過(guò)一個(gè)示例來(lái)演示如何使用這兩個(gè)方法來(lái)實(shí)現(xiàn)消息的轉(zhuǎn)發(fā)。
舉個(gè)例子:
還是剛才的工程矮锈,注釋掉forwardingTargetForSelector
的實(shí)現(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é)果如下:
可以看到,通過(guò)以上代碼的處理saySomething
消息也被轉(zhuǎn)發(fā)了苞笨。其實(shí)當(dāng)我們注釋了forwardInvocation
內(nèi)部實(shí)現(xiàn)债朵,也不會(huì)導(dǎo)致崩潰。
(文檔上的其他注意點(diǎn)總結(jié))其他注意點(diǎn):
-
forwardInvocation
可以查找響應(yīng)anInvocation
中的編碼的消息對(duì)象瀑凝,對(duì)于所有消息序芦,此對(duì)象不必相同 - 使用
anInvocation
將消息發(fā)送到該對(duì)象時(shí)anInvocation
將保存結(jié)果,運(yùn)行時(shí)系統(tǒng)將提取結(jié)果并將其傳遞給原始發(fā)送者 -
forwardInvocation
方法的實(shí)現(xiàn)不僅僅可以轉(zhuǎn)發(fā)消息粤咪,還可以合并響應(yīng)各種不同消息的代碼谚中,從而避免為每個(gè)選擇器編寫(xiě)單獨(dú)方法的麻煩。 -
forwardInvocation
方法對(duì)給定消息的響應(yīng)中不僅將其轉(zhuǎn)發(fā)給一個(gè)對(duì)象寥枝,還有可能涉及其他幾個(gè)對(duì)象 -
forwardInvocation
是NSObject
的方法宪塔,并且只會(huì)調(diào)用doesNotRecognizeSelector
方法,如果不實(shí)現(xiàn)doesNotRecognizeSelector
它不會(huì)轉(zhuǎn)發(fā)任何消息從而引起異常囊拜。
2.6 消息轉(zhuǎn)發(fā)流程圖
從動(dòng)態(tài)方法決議到消息的快速轉(zhuǎn)發(fā)某筐,再到消息的慢速轉(zhuǎn)發(fā)流程如下:
至此我們的消息轉(zhuǎn)發(fā)流程基本完畢
3. 總結(jié)
- 動(dòng)態(tài)方法決議有對(duì)象方法動(dòng)態(tài)解析
resolveInstanceMethod
和類方法動(dòng)態(tài)解析resolveClassMethod
兩種,都需要開(kāi)發(fā)者去實(shí)現(xiàn) - 消息轉(zhuǎn)發(fā)同樣分為快速消息轉(zhuǎn)發(fā)
forwardingTargetForSelector
和慢速消息轉(zhuǎn)發(fā)methodSignatureForSelector
- 慢速消息轉(zhuǎn)發(fā)同時(shí)還需要開(kāi)發(fā)者實(shí)現(xiàn)
forwardInvocation
方法 - 快速消息轉(zhuǎn)發(fā)是讓其他能響應(yīng)的對(duì)象來(lái)響應(yīng)未查找到的消息冠跷,對(duì)參數(shù)和返回值要求絕對(duì)匹配
- 慢速消息轉(zhuǎn)發(fā)提供了更加細(xì)粒度的控制南誊,首先會(huì)返回一個(gè)方法簽名給
runtime
,然后通過(guò)anInvocation
保存結(jié)果蔽莱,Runtime
會(huì)提取結(jié)果并將其傳遞給原始發(fā)送者
至此我們的消息或者說(shuō)方法弟疆,在Objective-C
的底層實(shí)現(xiàn)由objc_msgSend
開(kāi)始,探索了消息發(fā)送的流程盗冷,然后由消息找不到時(shí)的處理進(jìn)入到了動(dòng)態(tài)方法決議,然后通過(guò)_objc_msgForward_impcache
進(jìn)入到消息的轉(zhuǎn)發(fā)流程就結(jié)束了同廉,探索過(guò)程比較粗糙仪糖,也會(huì)有些瑕疵,如有問(wèn)題歡迎指正迫肖。锅劝。