美文网首页
Objective-C消息发送, objc_msgSend详解

Objective-C消息发送, objc_msgSend详解

作者: 你duck不必呀 | 来源:发表于2020-09-11 10:43 被阅读0次
    配图.jpg

    OC中,方法调用,会在编译后转换成objc_msgSend,等到程序运行时有Runtime系统调用

    [car run];
    

    转换成objc_msgSend如下:

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)car, sel_registerName("run"));
    

    objc_msgSend是runtime库中的方法,无法跳到对应的实现中,但是可以从苹果开源的源码中找到对应的实现:

    //These functions must be cast to an appropriate function pointer type  before being called. 
    
    OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    

    苹果在注释中提到:在调用这些函数之前,必须将它们转换为适当的函数指针类型。
    所以上面的转换objc_msgSend方法前会多出来((void (*)(id, SEL))(void *)

    源码中objc_msgSend是用汇编写的,简要总结如下:

    1.首先通过对象的isa找到类中的方法缓存列表cache_t

        ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
    
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // isa指向的 class
    

    2.从缓存中找SEL对应的IMP,如果从缓存找到,调用IMP(这段代码是汇编写的,因此这个过程被称作快速查找)

    .macro CacheHit
    .if $0 == NORMAL
        TailCallCachedImp x17, x12  // authenticate and call imp
    .elseif $0 == GETIMP
        mov p0, p17
        AuthAndResignAsIMP x0, x12  // authenticate imp and re-sign as IMP
        ret             // return IMP
    .elseif $0 == LOOKUP
        AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
        ret             // return imp via x17
    .else
    .abort oops
    .endif
    .endmacro
    

    3.如果CheckMiss,接下来从方法列表中找,(俗称慢速查找)

    .macro CheckMiss
        // miss if bucket->sel == 0
    .if $0 == GETIMP
        cbz p9, LGetImpMiss
    .elseif $0 == NORMAL
        cbz p9, __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        cbz p9, __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    

    _objc_msgLookup_uncached内部调用MethodTableLookup,有跳转到_class_lookupMethodAndLoadCache3

    bl  __class_lookupMethodAndLoadCache3 
    

    调用堆栈如下:

    _class_lookupMethodAndLoadCache3
    |  - lookUpImpOrForward
    

    4.都没找到:JumpMiss 走消息转发机制,调__objc_msgForward

    lookUpImpOrForward主要流程:

    查找缓存,因为慢速查找过程中也有在其他代码处已经查找到并且保存在缓存中了

    类如果未初始化,初始化它

    过程加锁 -->

    (1)从方法列表找IMP,如果没有走继承链查找

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

    (2)如果没有递归从继承链查找:父类的缓存,方法列表

    {
            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) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        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;
                    }
                }
                
                // 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;
                }
            }
        }
    

    (3)如果找到,填充缓存到当前对象缓存列表,返回IMP,本次查找结束

    log_and_fill_cache
    

    (4)如果一直找到根类NSObject都没找到,走方法决议过程

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

    (5)以上都没有,走消息转发

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

    _objc_msgForward_impcache,也是汇编实现的,其内部调用了_objc_forward_handler,定义如下:

    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    有没有很熟悉~~~

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

    解锁 <--

    3,4过程执行起来比较长,所以也被称作慢速查找

    总结:

    OC的方法都要经过objc_msgSend来调用,所以苹果用汇编实现,整个过程分为快速查找和慢速查找:
    快速查找直接从缓存中查找
    快速查找没找到的情况下,慢速查找从方法列表中,父类中找
    都没找到走消息 转发流程

    相关文章

      网友评论

          本文标题:Objective-C消息发送, objc_msgSend详解

          本文链接:https://www.haomeiwen.com/subject/tohaxhtx.html