欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS Runtime中如何通过SEL找到IMP

程序员文章站 2024-03-23 21:10:52
...

objc_msgSend(id self, SEL _cmd, ...) 中SEL找到IMP的流程

以下是苹果开源代码中Runtime底层的部分删减后的汇编代码

ENTRY _objc_msgSend
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class
    cmp x0, #0          // nil check and tagged pointer check
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
LGetIsaDone:
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

LNilOrTagged:
    b.eq    LReturnZero     // nil check
    END_ENTRY _objc_msgSend

1.首先会进入汇编中 ENTRY _objc_msgSend

2.如果指针小于等于 LNilOrTagged 直接return返回

3.通过isa 找到相应类class

4.CacheLookup : NORMAL
CacheLookup源码中注释为:
Locate the implementation for a selector in a class method cache.
在类的方法缓存中 为 SEL 定位到 IMP

5.此时全局搜索CacheLookup,在objc_msg_arm64.s中会找到其对应的宏CacheHitCheckMissadd

.macro CacheLookup

1:  CacheHit $0         // call or return imp 
    返回IMP,并执行 
    <1> MESSENGER_END_FAST 结束快速查找路径
    <2> GETIMP 返回IMP
    <3> LOOKUP 

2:  CheckMiss $0            // miss if bucket->sel == 0 
    执行
    <1> GETIMP LGetImpMiss 没找到IMP
    <2> __objc_msgSend_uncached
    <3> __objc_msgLookup_uncached 

3:  add x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)
    执行
    <1> CacheHit
    <2> CheckMiss
    <3> JumpMiss 

6.当执行__objc_msgSend_uncached时,说明没有相应缓存。将会在__objc_msgSend_uncached中执行MethodTableLookup

STATIC_ENTRY __objc_msgSend_uncached    
    MethodTableLookup
END_ENTRY __objc_msgSend_uncached

MethodTableLookup的宏定义中会发现__class_lookupMethodAndLoadCache3
全局搜索__class_lookupMethodAndLoadCache3发现没有什么多余的东西可以看,尝试去掉前边的_后发现新大陆!


汇编通过_class_lookupMethodAndLoadCache3方法跳转到了C,并且调用了lookUpImpOrForward


7.lookUpImpOrForward的调用传值

lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/ cache, YES/*resolver*/);

其中,cache传了NO,是由于先前汇编部分已经没有在缓存中找到对应的IMP,所以没有必要传YES

8.lookUpImpOrForward的调用过程

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    //再查一查缓存,万一有了呢
    //因为OC动态语言,随时随地都能操作修改,防止数据问题,再取一次
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    runtimeLock.read();
    //如果Class没有声明,就声明它
    if (!cls->isRealized()) {
        runtimeLock.unlockRead();
        runtimeLock.write();
        realizeClass(cls);
        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
    //如果Class没有初始化,就初始化它
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }
//开始反复尝试方法查找
 retry:    
    runtimeLock.assertReading();
    // Try this class's cache.
    //再查一查缓存,万一有了呢
    //因为OC动态语言,随时随地都能操作修改,防止数据问题,再取一次
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 在当前类的方法缓存列表中查找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 在当前类的父类的方法缓存列表中查找
    {
        unsigned attempts = unreasonableClassCount();
        //for循环,不断往父类查找,父类的父类,直到父类没有父类,
        //也就是直到curClass  == NSObject
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            //好像是防止越界
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            //查找父类的方法缓存
            imp = cache_getImp(curClass, sel);
            if (imp) {
                 //如果找到这个IMP,并且这个IMP不是消息转发的IMP
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 将这个IMP填充进缓存列表,避免下次重复这波操作
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // 大概意思应该是:
                    //发现一个可转发的方法,并且在父类中尝试
                    // Found a forward:: entry in a superclass.
                    // 停止搜索,不会缓存这个方法,但会调用
                    // Stop searching, but don't cache yet; call method 
                    // 优先执行动态方法解析
                    // resolver for this class first.
                    break;
                }
            }
            //在父类的方法缓存列表中找IMP
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //如果找到这个Meth,将这个IMP填充进缓存列表,避免下次重复这波操作
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //没发现IMP,尝试动态方法解析  !!一次!!
    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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;
    }
    //没发现这个IMP,且动态方法解析不起作用,就用消息转发。
    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    
    //调用消息转发
    imp = (IMP)_objc_msgForward_impcache;
    //缓存这个IMP
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

9.getMethodNoSuper_nolock方法调用过程,通过遍历class的方法列表查找IMP

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

10.动态方法解析 _class_resolveMethod

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    //判断class 是否是元类
    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);
        }
    }
}

动态解析实例方法 _class_resolveInstanceMethod

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    //判断是否有执行动态方法解析,如果不是就Return。
    //防止混乱。
    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.

    //系统自动给要解析的Class发送一条消息,这也是为什么
    //这个方法里面又调用了一次lookUpImpOrForward
    //+(BOOL)resolveInstanceMethod:(SEL)sel;会执行两次的原因
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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));
        }
    }
}

11.当lookUpImpOrForward没发现IMP,且尝试_class_resolveMethod动态方法解析不起作用时,调用消息转发。

iOS Runtime中如何通过SEL找到IMP

消息转发流程.png

 

消息转发的流程

消息转发流程开源代码中只调用了汇编的方法,没有具体的实现。
我们可以通过instrumentObjcMessageSends方法查看所有关于这个信息的调用堆栈信息。

extern void instrumentObjcMessageSends(BOOL);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        [LGPerson  walk];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

程序编译结束后,可在路径:硬盘 ->private -> tmp -> msgSends-xxxxx 文件 查看方法调用过程

 

iOS Runtime中如何通过SEL找到IMP

查看路径.png

 

iOS Runtime中如何通过SEL找到IMP

msg_Sends文件内容

1.forwardingTargetForSelector ,查询是否有其它对象能够处理该消息。在这个方法里,我们需要返回一个能够处理该消息的对象,如果还是无法处理消息,那么就会做最后的尝试。
2.先调用methodSignatureForSelector:获取方法签名。
3.然后再调用forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和forwardingTargetForSelector的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,会改变消息内容,比如增加参数,改变选择子等等。

相关标签: ios