美文网首页
objc_msgSend 分析

objc_msgSend 分析

作者: 小溜子 | 来源:发表于2020-09-18 17:52 被阅读0次

    在OC中,方法本质上又是什么?我们调用一个方法的时候究竟发生了什么?

    方法的本质

    我们新建一个项目,在main.m中实现入下代码。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            JKPerson *person = [[JKPerson alloc] init];
            [person saySomething];
        }
        return 0;
    }
    
    

    通过clang来编译这个main.m文件。

    clang -rewrite-objc main.m
    

    执行完这条命令后我们会发现,在当前mian.m所在的文件目录下生成了一个新的main.cpp文件。
    在main.cpp文件的最底部,我们发现我们main.m中main函数中的代码被编译成了如下形式。

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            JKPerson *person = ((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JKPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
        }
        return 0;
    }
    

    这段对象调用方法的代码

    [person saySomething];
    

    被编译成了如下形式

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
    

    也就是说我们的OC方法其本质上就是通过调用objc_msgSend函数来发送消息。
    接下来我们来看看在objc_msgSend中究竟做了什么事情。

    objc_msgSend

    我们通过给objc_msgSend下符号断点得知objc_msgSend函数在我们的libobjc.A.dylib中。
    接下来我们在libobjc.A.dylib中来查看我们的objc_msgSend源码。

    我们以objc-msg-arm64.s为研究对象。

    我们发现objc_msgSend使用汇编来实现的,为什么要用汇编来实现呢?有以下几点原因

    汇编更加容易被机器识别,效率更高。
    C语言中不可以通过一个函数来保留未知的参数并且跳转到任意的函数指针。C语言没有满足这些事情的必要特性。

    在objc_msgSend中摘取其中关键代码如下

        ENTRY _objc_msgSend
        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     // p16 = class
    LGetIsaDone:
        CacheLookup NORMAL 
        END_ENTRY _objc_msgSend
    

    我们可以看到在获取到Isa之后我们开启了方法的缓存查找流程

    LGetIsaDone:
        CacheLookup NORMAL 
    

    查找缓存

    我们摘取CacheLookup关键代码如下。

    .macro CacheLookup
        
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    3:  // wrap: p12 = first bucket, w11 = mask
        add p12, p12, w11, UXTW #(1+PTRSHIFT)
                                    // p12 = buckets + (mask << 1+PTRSHIFT)
    
        // Clone scanning loop to miss instead of hang when cache is corrupt.
        // The slow path may detect any corruption and halt later.
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    3:  // double wrap
        JumpMiss $0
        
    .endmacro
    

    我们可以看出其中有两个比较重要的,一个是CacheHit命中缓存,这个时候缓存命中了之后直接返回imp
    另一个是CheckMiss,我们来看看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_msgSend_uncached和__objc_msgLookup_uncached流程继续往下看

    STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p16 is the class to search
        
        MethodTableLookup
        TailCallFunctionPointer x17
    
        END_ENTRY __objc_msgSend_uncached
    
    STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p16 is the class to search
    。
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    

    我们可以看到他们都调用了一个叫MethodTableLookup的东西。
    摘取其中关键代码如下。

    .macro MethodTableLookup
        
        bl  __class_lookupMethodAndLoadCache3
    
    .endmacro
    

    流程图:


    image.png image.png

    至此我们关于objc_msgSend的汇编部分结束了,接下来将进入C/C++的查找流程。我们将在下篇文章中介绍objc_msgSend的慢速查找流程。

    相关文章

      网友评论

          本文标题:objc_msgSend 分析

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