美文网首页底层
iOS-OC底层07:objc_msgSend缓存中读取IMP

iOS-OC底层07:objc_msgSend缓存中读取IMP

作者: MonKey_Money | 来源:发表于2020-09-21 15:31 被阅读0次

    前沿

    我们探究OC的底层实现需要了解下面几个概念

    编译时: 即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
    运行时: 即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。
    可以说编译时是一个静态的阶段,类型错误很明显可以直接检查出来,可读性也好;而运行时则是动态的阶段,开始具体与运行环境结合起来

    Runtime有两个版本 ⼀个Legacy版本(早期版本) ,⼀个Modern版本(现⾏版本)

    • 早期版本对应的编程接⼝:Objective-C 1.0
    • 现⾏版本对应的编程接⼝:Objective-C 2.0
    • 早期版本⽤于Objective-C 1.0, 32位的Mac OS X的平台上
    • 现⾏版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序
      Objective-C Runtime Programming Guide
      我们怎么调用runtime时呢?三种方法分别时
      1.我们自己的代码:我们自己写的方法,[obj sayGoodBye]
      2.apple提供给我们的框架[@"123" isEqual:]
      3.直接调用runtime API:class_getInstanceMethod
      我们通过clang 编译main.m文件看方法怎么调用的
    clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.6.sdk main.m
    //我们看到关键代码objc_msgSend
       ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
    

    在源码中探究objc_msgSend的执行流程

    objc_msgSend汇编语言执行过程

    汇编主要代码

    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     // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSend
    #if SUPPORT_TAGGED_POINTERS
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
        // tagged
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
        add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
        cmp x10, x16
        b.ne    LGetIsaDone
        // ext tagged
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
        END_ENTRY _objc_msgSend
        ENTRY _objc_msgLookup
        UNWIND _objc_msgLookup, NoFrame
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
    #else
        b.eq    LLookup_Nil
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    LLookup_GetIsaDone:
        // returns imp
        CacheLookup LOOKUP, _objc_msgLookup
    
    #if SUPPORT_TAGGED_POINTERS
    LLookup_NilOrTagged:
        b.eq    LLookup_Nil // nil check
        // tagged
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
        add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
        cmp x10, x16
        b.ne    LLookup_GetIsaDone
    
    

    汇编语言解析
    1.判断空

        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
    

    判断接收者是否为空,如果为空,根据arm64和arm64_32做不同的处理,
    如果是小对象,则进行LNilOrTagged

    2.通过isa 获取class

    ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13 
    

    3.获取isa成功,查询
    CacheLookup NORMAL, _objc_msgSend
    大致流程图如下


    objc_msgSend.png

    GetClassFromIsa_p16从isa中获取class

    汇编代码如下

    .macro GetClassFromIsa_p16 /* src */
    #if SUPPORT_INDEXED_ISA
        // Indexed isa
        mov p16, $0         // optimistically set dst = src
        tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
        // isa in p16 is indexed
        adrp    x10, _objc_indexed_classes@PAGE
        add x10, x10, _objc_indexed_classes@PAGEOFF
        ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
        ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
    1:
    
    #elif __LP64__
        // 64-bit packed isa
        and p16, $0, #ISA_MASK
    
    #else
        // 32-bit raw isa
        mov p16, $0
    
    #endif
    
    .endmacro
    

    根据不同的架构,从isa中获取class类结构分析

    CacheLookup 从缓存中读方法

    .macro CacheLookup
    LLookupStart$1:
    
        // p1 = SEL, p16 = isa
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        and p10, p11, #~0xf         // p10 = buckets
        and p11, p11, #0xf          // p11 = maskShift
        mov p12, #0xffff
        lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
        and p12, p1, p11                // x12 = _cmd & mask
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                         // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
        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:  // wrap: p12 = first bucket, w11 = mask
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        add p12, p12, p11, LSL #(1+PTRSHIFT)
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
        // 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
    
    LLookupEnd$1:
    LLookupRecover$1:
    3:  // double wrap
        JumpMiss $0
    
    .endmacro
    
    
    1. 获取mask和buckets,在arms64下,mask和buckets存在一块,通过位运算取值
      我们在从isa的到class的时候就是运用这个方法
      ldr p11, [x16, #CACHE] // p11 = mask|buckets
      2.通过位运算获取buckets和方法hash值
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR 
    

    3.通过获取到的hash值,获取hash值对应的bucket

        add p12, p10, p12, LSL #(1+PTRSHIFT)
                         // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
    CacheLookup.png

    相关文章

      网友评论

        本文标题:iOS-OC底层07:objc_msgSend缓存中读取IMP

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