IOS底層原理之動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)

一、前言

在OC中方法的調(diào)用都是轉(zhuǎn)化為objc_msgSend函數(shù)的調(diào)用的。在我上一篇文章深入?yún)R編探索objc_msgSend中對(duì)objc_msgSend底層的匯編源碼進(jìn)行了分析,已然知道了方法發(fā)送流程。

1、首先從消息接受者receiverClass的cache中查找方法,如果找到方法,則直接調(diào)用。
2、如果在receiverClass的cache中沒(méi)有找到方法,則從receiverClass的方法列表中查找方法,如果找到方法則將方法緩存到cache并調(diào)用方法。
3、如果在receiverClass的方法列表中沒(méi)有找到,則從receiverClass父類的緩存中查找,如果在緩存中有找到,則會(huì)先判斷是否是消息轉(zhuǎn)發(fā)的方法。
4、如果是消息轉(zhuǎn)發(fā)的方法則會(huì)走消息轉(zhuǎn)發(fā)的流程,終止方法的查找。
5、如果是非消息轉(zhuǎn)發(fā)的方法則會(huì)調(diào)用log_and_fill_cache進(jìn)行方法的緩存,終止方法的查找并調(diào)用方法。
6、如果在父類的緩存中沒(méi)有找到,則會(huì)從父類的方法列表中查找,如果找到了則會(huì)調(diào)用log_and_fill_cache進(jìn)行方法的緩存,終止方法的查找并調(diào)用方法。
7、如果在父類的方法列表中沒(méi)有找到,重復(fù)執(zhí)行3、5、6步驟,直到父類為nil為止。
8、如果直到父類為nil還是未能找到方法的實(shí)現(xiàn),則會(huì)走動(dòng)態(tài)方法解析流程。

下面分析動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)。

二、動(dòng)態(tài)方法解析

在上一篇文章深入?yún)R編探索objc_msgSend中,方法的查找流程在lookUpImpOrForward方法中實(shí)現(xiàn),在消息接受者receiverClass本身及其父類都未實(shí)現(xiàn)方法情況下,開(kāi)始動(dòng)態(tài)方法解析。動(dòng)態(tài)方法解析其實(shí)是蘋果程序員的一種容錯(cuò)手段。下面我們來(lái)看下lookUpImpOrForward方法中動(dòng)態(tài)方法解析部分的代碼。

if (resolver  &&  !triedResolver) {
    runtimeLock.unlock();
    _class_resolveMethod(cls, sel, inst);
    runtimeLock.lock();
    // Don't cache the result; we don't hold the lock so it may have 
    // changed already. Re-do the search from scratch instead.
    triedResolver = YES;
    goto retry;
}

在這段代碼中調(diào)用了_class_resolveMethod方法,下面是_class_resolveMethod方法的源碼。

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

這里有一個(gè)判斷當(dāng)前類cls是否是元類的條件,同時(shí)我們知道對(duì)象方法是存儲(chǔ)類中的,類方法存儲(chǔ)在元類中,由此看這里的判斷并不奇怪,用以區(qū)分對(duì)象方法和類方法的動(dòng)態(tài)解析。

1、對(duì)象方法的動(dòng)態(tài)解析

我們先來(lái)看看對(duì)象方法的動(dòng)態(tài)解析。以下是對(duì)象方法動(dòng)態(tài)解析_class_resolveInstanceMethod的源碼。

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
  ...
}

在這段代碼中調(diào)用了lookUpImpOrNil方法,傳入cls的元類(cls->ISA()),為什么這里要傳元類而不是cls本身呢?我們來(lái)看下lookUpImpOrNil方法的實(shí)現(xiàn)。

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

lookUpImpOrNil方法內(nèi)部其實(shí)調(diào)用了lookUpImpOrForward方法,而lookUpImpOrForward方法是查找方法的代碼實(shí)現(xiàn)。在這里查找的是resolveInstanceMethod這樣的一個(gè)系統(tǒng)約定的方法,這個(gè)方法在NSObject中有實(shí)現(xiàn),而且是一個(gè)類方法,這就是為什么傳入元類而非當(dāng)前類的原因。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

接下來(lái)調(diào)用了objc_msgSend向當(dāng)前類cls中發(fā)送了一個(gè)消息,如果消息發(fā)送成功則說(shuō)明當(dāng)前類中重寫(xiě)了NSObject的resolveInstanceMethod方法,再次調(diào)用lookUpImpOrForward方法重新執(zhí)行查找方法實(shí)現(xiàn)的流程。

2、類方法的動(dòng)態(tài)解析

以下是類方法動(dòng)態(tài)解析_class_resolveClassMethod的源碼。

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}

其實(shí)類方法動(dòng)態(tài)解析和對(duì)象方法動(dòng)態(tài)解析的流程是一模摸一樣的,不同的是類方法動(dòng)態(tài)解析是在resolveClassMethod上面做文章的,而且在類方法動(dòng)態(tài)解析未做處理的時(shí)候會(huì)走對(duì)象方法動(dòng)態(tài)解析的流程,這是因?yàn)轭惙椒ù嬖谠愔校鳱SObject的元類繼承自NSObject,所以在類方法的巡查過(guò)程中,通過(guò)元類的繼承關(guān)系可能會(huì)最終找到NSObject類。

 _class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,  NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
{
    _class_resolveInstanceMethod(cls, sel, inst);
}

3、動(dòng)態(tài)方法解析實(shí)踐

@interface Person : NSObject
- (void)sayHappy;
+ (void)sayLove;
@end

@implementation Person

@end

上面這段代碼定義一個(gè)Person類,在該類中定義一個(gè)對(duì)象方法sayHappy和一個(gè)類方法sayLove,這兩個(gè)方法都未實(shí)現(xiàn),那么我們?cè)谡{(diào)用這個(gè)兩個(gè)方法的時(shí)候程序必定會(huì)崩潰。那么只需要在Person類中重寫(xiě)resolveInstanceMethodresolveClassMethod方法去實(shí)現(xiàn)動(dòng)態(tài)方法解析,程序便不會(huì)崩潰。

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel == @selector(sayHappy)){
        IMP sayHappyImp = class_getMethodImplementation(self, @selector(happy));
        Method happyMethod = class_getInstanceMethod(self, @selector(happy));
        const char *types = method_getTypeEncoding(happyMethod);
        class_addMethod(self, sel, sayHappyImp, types);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(sayLove)){
        Class class = object_getClass(self);//元類
        IMP sayLoveImp = class_getMethodImplementation(class, @selector(love));
        Method loveMethod = class_getClassMethod(class, @selector(love));
        const char *types = method_getTypeEncoding(loveMethod);
        class_addMethod(class, sel, sayLoveImp, types);
        return YES;
    }
    return [super resolveClassMethod:sel];
}

-(void)happy{
    NSLog(@"I am happy!");
}

+(void)love{
    NSLog(@"love you!");
}

@end

這樣一來(lái)sayHappy方法的調(diào)用會(huì)轉(zhuǎn)化為happy方法的調(diào)用,sayLove方法的調(diào)用會(huì)轉(zhuǎn)化為love方法的調(diào)用。


三、消息轉(zhuǎn)發(fā)

如果一個(gè)方法的調(diào)用在消息分發(fā)階段沒(méi)有找到對(duì)應(yīng)的方法也未做動(dòng)態(tài)方法解析處理,這個(gè)時(shí)候就會(huì)走消息轉(zhuǎn)發(fā)流程。

imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

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

先來(lái)看一下消息轉(zhuǎn)發(fā)的流程。


1、調(diào)用forwardingTargetForSelector方法,如果返回值不為nil,則調(diào)用objc_msgSend進(jìn)行消息發(fā)送。
2、如果forwardingTargetForSelector方法返回nil,則會(huì)調(diào)用methodSignatureForSelector方法獲取方法簽名,如果返回nil則說(shuō)明消息無(wú)法處理,調(diào)用doesNotRecognizeSelector方法
3、如果調(diào)用methodSignatureForSelector方法成功獲取方法簽名,則調(diào)用forwardInvocation方法,開(kāi)發(fā)者可以在forwardInvocation方法中自定義邏輯。
4、這里只是關(guān)于對(duì)象方法的消息轉(zhuǎn)發(fā),類方法的消息轉(zhuǎn)發(fā)流程是一樣的。

2、消息轉(zhuǎn)發(fā)實(shí)踐

如之前代碼所示,在Person類中并沒(méi)有實(shí)現(xiàn)sayHappy這個(gè)方法,如果在沒(méi)有動(dòng)態(tài)方法解析處理的情況下,調(diào)用這個(gè)方法程序必定會(huì)崩潰,但是如果實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)的處理,那么就可以將方法的調(diào)用轉(zhuǎn)交其他的對(duì)象來(lái)處理。

定義一個(gè)類OtherHelper,定義一個(gè)sayHappy方法并實(shí)現(xiàn)該方法。

@interface OtherHelper : NSObject
-(void)sayHappy;
@end

@implementation OtherHelper
- (void)sayHappy{
    NSLog(@"happly:%s",__func__); 
}
@end

然后再Person類中進(jìn)行消息轉(zhuǎn)發(fā)處理。

@implementation Person

- (id)forwardingTargetForSelector:(SEL)aSelector{
    OtherHelper *helper = [[OtherHelper alloc]init];
    if([helper respondsToSelector:aSelector]){
        return helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

在Person類中重寫(xiě)forwardingTargetForSelector方法,返回OtherHelper的實(shí)例對(duì)象,這樣Person的sayHappy方法調(diào)用就會(huì)轉(zhuǎn)交OtherHelper的sayHappy處理。

如果forwardingTargetForSelector方法返回nil,那么還有可以重寫(xiě)methodSignatureForSelectorforwardInvocation進(jìn)行消息轉(zhuǎn)發(fā)處理。

@implementation Person

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
   if(aSelector == @selector(sayHappy)){
//        return [[OtherHelper alloc]methodSignatureForSelector:aSelector];
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
   NSLog(@"forwardInvocation");
   OtherHelper *helper = [OtherHelper new];
   if([helper respondsToSelector:anInvocation.selector]){
       [anInvocation invokeWithTarget:helper];
   }else{
       [super forwardInvocation:anInvocation];
   }
}

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

相關(guān)閱讀更多精彩內(nèi)容

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