系统底层源码分析(19)——动态方法决议&消息转发

接着上篇文章(系统底层源码分析(18)——objc_msgSend)继续说:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...
    if (!cls->isRealized()) {
        realizeClass(cls);//准备-父类
    }
    ...
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);//从缓存获取imp
    if (imp) goto done;//找到返回
    //缓存中没有找到就去查找方法列表
    // Try this class's method lists.
    {//局部作用域,可避免名字冲突
        Method meth = getMethodNoSuper_nolock(cls, sel);//查找
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);//缓存
            imp = meth->imp;
            goto done;
        }
    }
    //子类找不到就递归找父类
    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) { ... }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);//从父类缓存获取imp
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            //缓存中没有找到就去查找父类方法列表
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);//查找
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);//缓存
                imp = meth->imp;
                goto done;
            }
        }
    }
    
    // No implementation found. Try method resolver once.

    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;//再次查找
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

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

 done:
    runtimeLock.unlock();

    return imp;
}
  • 报错
  1. 之前说到调用方法,底层会调用objc_msgSend,进入快速查找流程,找不到就进入慢速查找流程,然后来到lookUpImpOrForward,如果最后还找不到,就会调用_objc_msgForward_impcache报错:
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret

beq __objc_msgForward
b   __objc_msgForward_stret

END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward
// Non-stret version

MI_GET_EXTERN(r12, __objc_forward_handler)
ldr r12, [r12]
bx  r12

END_ENTRY __objc_msgForward

上述是汇编代码,调用流程是:

__objc_msgForward_impcache -> __objc_msgForward -> __objc_forward_handler

  1. __objc_forward_handler会回到源码中,查找时需要去掉一个下划线:
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);//打印的错误信息
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

打印信息是不是很熟悉,就是我们调用没有实现的方法时报的错:

报错信息
  • 动态方法决议
  1. 如果调用没有实现的方法就会报错,但是系统给了挽救的机会,就在报错前进行处理的_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*/)) 
        {//根据isa走位图,查找实例方法和类方法最终都走向NSObject类,所以再进行一次实例方法决议
            _class_resolveInstanceMethod(cls, sel, inst);//实例方法决议
        }
    }
}
  1. 我们先来看对实例方法的处理,首先会查找resolveInstanceMethod
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) //查找resolveInstanceMethod并缓存
    {
        // 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*/);//再找一次
    ...
}
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;
}
  1. 找到之后因为会缓存,所以下次查找时,可以通过objc_msgSend更快查找并调用,这里要注意找的是系统方法resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
  1. 这时可以通过在Person类中重写resolveInstanceMethod来进行处理(动态添加方法):
//Person类中
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say)) {
        IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
        return class_addMethod(self, sel, sayHelloIMP, sayHelloType);
    }
    return [super resolveInstanceMethod:sel];
}
  1. 返回后,回到第2步,这时再次lookUpImpOrNil,由于动态添加了方法,所以这次可以找到方法,然后返回后就回到第1步进行retry,重新查找并找到,所以就不会报错。

  2. 另外类方法处理也是差不多:

/***********************************************************************
* _class_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 _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) //查找resolveClassMethod并缓存
    {
        // 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*/);
    ...
}
//Person类中
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(cls_say)) {
        IMP sayHelloIMP = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(cls_sayHello));
        Method sayHelloMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(cls_sayHello));
        const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, sayHelloIMP, sayHelloType);
    }
    return [super resolveClassMethod:sel];
}
  • 消息转发

如果动态方法决议也没有处理,就会进入消息转发流程(简单来说就是交给别人处理):

  1. 首先会进入快速转发:
//Person类中
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(say)) {
        return [Teacher alloc];//返回对象
    }
    return [super forwardingTargetForSelector:aSelector];
}
  1. 如果快速转发也没有处理,就进入慢速转发流程:

2-1. methodSignatureForSelector返回SEL方法的签名,返回的签名是根据方法的参数来封装的。这个函数让重载方有机会抛出一个函数的签名,用于生成NSInvocation,再由后面的 forwardInvocation去执行:

//Person类中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(say)) { 
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
    }
    return [super methodSignatureForSelector:aSelector];
}

2-2. 在forwardInvocation里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(而forwarding TargetForSelectorSelec只能以Selector的形式转向一个对象):

//Person类中
- (void)forwardInvocation:(NSInvocation *)anInvocation{
   SEL aSelector = [anInvocation selector];
   Teacher *teacher = [Teacher alloc];
   if ([teacher respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:teacher];
   else
       [super forwardInvocation:anInvocation];
}
  • 实现forwardInvocation后,就算不处理应用也不会再崩溃。
没有实现的方法处理流程图
  • 补充
  1. 为了更充分说明这些流程的有效性,我们可以通过instrumentObjcMessageSends获取系统日志:
extern void instrumentObjcMessageSends(BOOL flag);//系统方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *student = [Student alloc] ;
        instrumentObjcMessageSends(true);//路径在/tmp/
        [student say];
        instrumentObjcMessageSends(false);
    }
    return 0;
}
  1. 然后直接前往/tmp(文件夹路径),然后打开msgSends-前缀的文件:
//动态方法决议
+ Student NSObject resolveInstanceMethod:
+ Student NSObject resolveInstanceMethod:
//消息转发-快速
- Student NSObject forwardingTargetForSelector:
- Student NSObject forwardingTargetForSelector:
//消息转发-慢速
- Student Student methodSignatureForSelector:
- Student Student methodSignatureForSelector:
...
//还会调用一次resolveInstanceMethod,因为methodSignatureForSelector返回的签名需要查找方法来匹配,所以进行lookUpImpOrForward查询导致又调用了resolveInstanceMethod
+ Student NSObject resolveInstanceMethod:
+ Student NSObject resolveInstanceMethod:
//
- Student Student forwardInvocation:

+ NSInvocation NSObject initialize
+ NSInvocation NSInvocation _invocationWithMethodSignature:frame:
- NSMethodSignature NSMethodSignature frameLength
...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容