How to find IMP through SEL in iOS Runtime

The process of SEL finding IMP in objc_msgSend(id self, SEL _cmd, ...)

The following is the assembly code after deletion of the underlying part of Runtime in Apple's open source code

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. First enter the assembly ENTRY _objc_msgSend

2. If the pointer is less than or equal LNilOrTaggeddirectly returnreturned

3. Pass to isafind the corresponding classclass

4. The CacheLookup : NORMAL
CacheLookupsource is annotated:
. A for the Locate The Selector Implementation in Method A Cache class
class method as cache SELtarget IMP.

5. At this time the global search CacheLookup, which will be found in the corresponding macro objc_msg_arm64.s CacheHit, CheckMiss, add.

.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. When executed __objc_msgSend_uncached, there is no corresponding cache. Will be __objc_msgSend_uncachedimplemented in MethodTableLookup.

STATIC_ENTRY __objc_msgSend_uncached    
    MethodTableLookup
END_ENTRY __objc_msgSend_uncached

In MethodTableLookupthe macro definition, you will find that there is nothing superfluous to see through the __class_lookupMethodAndLoadCache3
global search __class_lookupMethodAndLoadCache3. Try to remove the front _and discover the new world!


The assembly _class_lookupMethodAndLoadCache3jumped to C through the method, and calledlookUpImpOrForward


7. lookUpImpOrForwardCall by value

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

Among them, cachepassed NO, because the previous compilation part has not found the corresponding IMP in the cache, so there is no need to pass YES.

8. lookUpImpOrForwardThe calling process

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_nolockMethod call process, classfind IMP through the traversed method list

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. Dynamic method analysis _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);
        }
    }
}

Dynamically resolve instance methods _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. When lookUpImpOrForwardno IMP is found, and the attempt to _class_resolveMethodresolve the dynamic method does not work, call message forwarding.

Message forwarding process.png

 

Message forwarding process

Only the assembly method is called in the open source code of the message forwarding process, and there is no specific implementation.
We can instrumentObjcMessageSendsview all the call stack information about this information through the method.

extern void instrumentObjcMessageSends(BOOL);

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

After the program is compiled, you can view the method calling process in the path: hard disk->private -> tmp -> msgSends-xxxxx file

 

View path.png

 

msg_Sends file content

1. forwardingTargetForSelectorQuery whether there are other objects that can handle the message. In this method, we need to return an object that can handle the message. If the message still cannot be processed, then we will make the last attempt.
2. Call the methodSignatureForSelector:method signature first .
3. Then call forwardInvocation:for processing. The processing of this step can be directly forwarded to other objects, that is, forwardingTargetForSelectorthe effect is equivalent, but few people do this, because the later the message processing, the greater the cost of processing the message, and the performance The greater the overhead. Therefore, in this way, the content of the message will be changed, such as adding parameters, changing selectors, and so on.

Guess you like

Origin blog.csdn.net/wangletiancsdn/article/details/104900328