美文网首页
Objc_msgSend流程

Objc_msgSend流程

作者: _涼城 | 来源:发表于2020-09-21 17:08 被阅读0次

     在了解objc_msgSend之前,需要先了解runtime

      Runtime是一套底层纯C语言APIObjective-C代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式。那么什么是运行时呢?

     运行时就是程序已经装载在内存中,与编译时不同的是,运行时在内存中做操作以及判断。

     Objective-C是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。

    runtime简析

    官方文档介绍# Objective-C Runtime Programming Guide

    调用方式
    • Objective-C Code
    • Framework & Service,例isKindofClass
    • Runtime API,例class_getInstanceSize
    下层调用

    Compilerllvm

    Runtime System Library运行时系统

    探索objc_msgSend
    1. 创建一个Command line Tool工程,在main.m文件实现以下内容

      @interface Person : NSObject
      
      - (void)say;
      
      @end
      
      @implementation Person
      
      - (void)say{
          NSLog(@"say");
      }
      
      @end
      
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              // insert code here...
              Person *person = [Person alloc];
              [person say];
      
          }
          return 0;
      }
      
      
    2. 通过Clangmain.m文件编译成main.cpp

      注意 ios以及sdk版本取决于本地环境

      clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk main.m
      
    3. 搜索int main,找到main函数

      int main(int argc, const char * argv[]) {
      /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
      
         Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
         ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say"));
      
      }
      return 0;
      }
      
      • sel_registerName = @selector()= NSSelectorFromString
      • objc_msgSend(),发送的消息(消息的接受者,消息的主题)
    通过objc_msgSend实现方法调用
    1. 引入#import <objc/message.h>头文件

    2. buildSettings搜索msg_send,设置为false

      objc_msgSend.png
    1. 调用objc_msgSend

      objc_msgSend(person,sel_registerName("say"));
      
    2. 结果调用成功

      调用objc_msgSend.png
    通过objc_msgSendSuper调用父类方法
    1. 新增一个Teacher类继承于Person类。

      @interface Teacher : Person
      @end
      @implementation Teacher
      @end
      
    2. Person类新增一个sayHello方法

      - (void)sayHello;
      //...
      -(void)sayHello{
          NSLog(@"sayHello");
      }
      
    3. 初始化结构体objc_super,调用objc_msgSendSuper

      Teacher *teacher = [Teacher alloc];
      struct objc_super send_super;
      send_super.receiver = teacher;
      send_super.super_class = [Person class];
      objc_msgSendSuper(&send_super, sel_registerName("sayHello"));
      
    4. 最终输出结果

      objc_msgSendSuper结果.png
    探索objc_msgSend(快速查找流程)
    objc_msgSend源码

     在objc源码中找到objc-msg-arm64.s文件,搜索_objc_msgSend,找到ENTRY _objc_msgSend

    //---- 消息发送 -- 汇编入口--objc_msgSend主要是拿到接收者的isa信息
        ENTRY _objc_msgSend 
    //---- 无窗口
        UNWIND _objc_msgSend, NoFrame 
    
    //---- p0 和空对比,即判断接收者是否存在,其中p0是objc_msgSend的第一个参数-消息接收者receiver
        cmp p0, #0          // nil check and tagged pointer check 
    //---- le小于 --支持taggedpointer(小对象类型)的流程
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) 
    #else
    //---- p0 等于 0 时,直接返回 空
        b.eq    LReturnZero 
    #endif 
    //---- p0即receiver 肯定存在的流程
    //---- 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器
        ldr p13, [x0]       // p13 = isa 
    //---- 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
        GetClassFromIsa_p16 p13     // p16 = class 
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached 
    //---- 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程
        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
    
    1. cmp p0 判断对象是否为空;

    2. 如果不支持TAGGED_POINTERS,则返回空;

    3. 根据对象获取isa,最终获取classp16;

    4. 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程

      CacheLookup NORMAL, _objc_msgSend
      
    CacheLookup

    在文件中搜索.macro 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. ldr p11, [x16, #CACHE] // p11 = mask|buckets 平移16位获取Cache
    2. 获取bucketsmask
    3. 比较获取的bucketselobjc_msgSend的第二个参数的_cmd(即p1)是否相等,如果相等调用CacheHit,否则直接跳转至最后一个(查找方式永远是向前查找),继续循环,一直到CheckMiss满足条件。
    CheckMiss

    Normal时,调用__objc_msgLookup_uncached函数

    .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
    
    JumpMiss

    Normal时,调用__objc_msgLookup_uncached函数

    .macro JumpMiss
    .if $0 == GETIMP
        b   LGetImpMiss
    .elseif $0 == NORMAL
        b   __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        b   __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    
    __objc_msgLookup_uncached

    MethodTableLookup 查询方法列表

    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
    
    MethodTableLookup

    调用_lookUpImpOrForward慢速查找流程

    .macro MethodTableLookup
    
        // push frame
        SignLR
        stp fp, lr, [sp, #-16]!
        mov fp, sp
    
        // save parameter registers: x0..x8, q0..q7
        sub sp, sp, #(10*8 + 8*16)
        stp q0, q1, [sp, #(0*16)]
        stp q2, q3, [sp, #(2*16)]
        stp q4, q5, [sp, #(4*16)]
        stp q6, q7, [sp, #(6*16)]
        stp x0, x1, [sp, #(8*16+0*8)]
        stp x2, x3, [sp, #(8*16+2*8)]
        stp x4, x5, [sp, #(8*16+4*8)]
        stp x6, x7, [sp, #(8*16+6*8)]
        str x8,     [sp, #(8*16+8*8)]
    
        // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward //核心源码
    
        // IMP in x0
        mov x17, x0
    
        // restore registers and return
        ldp q0, q1, [sp, #(0*16)]
        ldp q2, q3, [sp, #(2*16)]
        ldp q4, q5, [sp, #(4*16)]
        ldp q6, q7, [sp, #(6*16)]
        ldp x0, x1, [sp, #(8*16+0*8)]
        ldp x2, x3, [sp, #(8*16+2*8)]
        ldp x4, x5, [sp, #(8*16+4*8)]
        ldp x6, x7, [sp, #(8*16+6*8)]
        ldr x8,     [sp, #(8*16+8*8)]
    
        mov sp, fp
        ldp fp, lr, [sp], #16
        AuthenticateLR
    
    .endmacro
    
    objc_msgSend流程图
    objc_msgSend流程分析.png

    相关文章

      网友评论

          本文标题:Objc_msgSend流程

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