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

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ì)是做了一次方法的決議操作

  1. 初始化一個(gè)selresolveInstanceMethod
  2. 然后查找該sel,找到后則繼續(xù)處理(找到說(shuō)明實(shí)現(xiàn)了該方法)配深,找不到就直接返回
  3. 通過(guò)objc_msgSend發(fā)送消息携添,這里發(fā)送的是resolveInstanceMethod消息,如果返回YES則說(shuō)明該方法被實(shí)現(xiàn)篓叶,否則未實(shí)現(xiàn)烈掠。
  4. 如果實(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)部將saySomethingimp指定為我們已經(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)部將sayLoveimp指定為我們已經(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);
}
isa流程圖

由這個(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

我們搜索_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ù)我們可以看到晃危,在objcMsgLogEnabledtrue的時(shí)候日志會(huì)輸出到/tmp/msgSends-xxx的目錄下叙赚。那么該如何讓objcMsgLogEnabledtrue呢,我們先不妨搜索一下僚饭,搜完后我們發(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目錄下

日志的存儲(chǔ)路徑

日志結(jié)果

首先我們就看到了我們熟悉的resolveInstanceMethod偿乖,緊接著就是forwardingTargetForSelectormethodSignatureForSelector這兩個(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)日志

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

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

forwardingTargetForSelector

根據(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é)果如下:

打印結(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)引用近顷。

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.

譯:如果你在一個(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é):

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

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

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

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)去看看

See Also
forwardInvocation

根據(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è)選擇器遥诉。

顯然methodSignatureForSelectorforwardInvocation是要一起出現(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é)果如下:

打印結(jié)果

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

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

  1. forwardInvocation可以查找響應(yīng)anInvocation中的編碼的消息對(duì)象瀑凝,對(duì)于所有消息序芦,此對(duì)象不必相同
  2. 使用anInvocation將消息發(fā)送到該對(duì)象時(shí)anInvocation將保存結(jié)果,運(yùn)行時(shí)系統(tǒng)將提取結(jié)果并將其傳遞給原始發(fā)送者
  3. forwardInvocation方法的實(shí)現(xiàn)不僅僅可以轉(zhuǎn)發(fā)消息粤咪,還可以合并響應(yīng)各種不同消息的代碼谚中,從而避免為每個(gè)選擇器編寫(xiě)單獨(dú)方法的麻煩。
  4. forwardInvocation方法對(duì)給定消息的響應(yīng)中不僅將其轉(zhuǎn)發(fā)給一個(gè)對(duì)象寥枝,還有可能涉及其他幾個(gè)對(duì)象
  5. forwardInvocationNSObject的方法宪塔,并且只會(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ā)流程圖

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

3. 總結(jié)

  1. 動(dòng)態(tài)方法決議有對(duì)象方法動(dòng)態(tài)解析resolveInstanceMethod和類方法動(dòng)態(tài)解析resolveClassMethod兩種,都需要開(kāi)發(fā)者去實(shí)現(xiàn)
  2. 消息轉(zhuǎn)發(fā)同樣分為快速消息轉(zhuǎn)發(fā)forwardingTargetForSelector和慢速消息轉(zhuǎn)發(fā)methodSignatureForSelector
  3. 慢速消息轉(zhuǎn)發(fā)同時(shí)還需要開(kāi)發(fā)者實(shí)現(xiàn)forwardInvocation方法
  4. 快速消息轉(zhuǎn)發(fā)是讓其他能響應(yīng)的對(duì)象來(lái)響應(yīng)未查找到的消息冠跷,對(duì)參數(shù)和返回值要求絕對(duì)匹配
  5. 慢速消息轉(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)題歡迎指正迫肖。锅劝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蟆湖,隨后出現(xiàn)的幾起案子故爵,更是在濱河造成了極大的恐慌,老刑警劉巖隅津,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诬垂,死亡現(xiàn)場(chǎng)離奇詭異劲室,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)结窘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門很洋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人隧枫,你說(shuō)我怎么就攤上這事喉磁。” “怎么了官脓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵协怒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我卑笨,道長(zhǎng)孕暇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任湾趾,我火速辦了婚禮芭商,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搀缠。我一直安慰自己铛楣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布艺普。 她就那樣靜靜地躺著簸州,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歧譬。 梳的紋絲不亂的頭發(fā)上岸浑,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音瑰步,去河邊找鬼矢洲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缩焦,可吹牛的內(nèi)容都是我干的读虏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼袁滥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盖桥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起题翻,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揩徊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體塑荒,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熄赡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袜炕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片本谜。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖偎窘,靈堂內(nèi)的尸體忽然破棺而出乌助,到底是詐尸還是另有隱情,我是刑警寧澤陌知,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布他托,位于F島的核電站,受9級(jí)特大地震影響仆葡,放射性物質(zhì)發(fā)生泄漏赏参。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一沿盅、第九天 我趴在偏房一處隱蔽的房頂上張望把篓。 院中可真熱鬧,春花似錦腰涧、人聲如沸韧掩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疗锐。三九已至,卻和暖如春费彼,著一層夾襖步出監(jiān)牢的瞬間滑臊,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工箍铲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雇卷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓颠猴,卻偏偏與公主長(zhǎng)得像聋庵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芙粱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349