Runtime底層原理探究(一) --- 消息發送機制(快速查找)

OC是一門動態語言全部的方法都是由運行時進行。 Objective-C 語言將決定儘量的從編譯和連接時推遲到運行時。只要有可能,Objective-C 老是使用動態 的方式來解決問題。這意味着 Objective-C 語言不只須要一個編譯器,同時也須要一個運行時系統來執行 編譯好的代碼。這兒的運行時系統扮演的角色相似於 Objective-C 語言的操做系統,Objective-C 基於該系統來工做。 Runtime的做用是 能動態產生/修改一個類,一個成員變量,一個方法c++

Runtime調用有三種方式緩存

  1. NSObject(peformselector)
  2. Selector(底層會轉換爲objc_msgSend())
  3. Runtime的Api

objc_msg_send()

咱們知道OC的函數調用是消息發送機制,那麼消息發送機制是如何實現的呢。bash

Animals * animal = [[Animals alloc]init];
[animal eat];
複製代碼

將該文件編譯成c++文件經過
clang-rewrite-objc 文件名 -o test.c++
命令 一共9w多行代碼只需看最後函數

// -(void) eat;
/* @end */

#pragma clang assume_nonnull end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b2_hs7ds2bd5zz7d752kk495bhw0000gn_T_main_f668c6_mi_0);

        Animals * animal = ((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animals"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)animal, sel_registerName("eat"));

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼

objc_msgSend(void /* id self, SEL op, ... */ ) 當類初始化時候 顯示獲取 id self,即 (id)objc_getClass("Animals"),就是根據類名取獲取這個類,而後alloc,init就是 #selector(alloc) 其底層實現是 sel_registerName("alloc/init"),其目的就是爲了查找該類裏面有沒該方法 第二句同理target是已經生產的animal selector是 eat方法 sel_registerName("eat")去類的內存佈局中查找eat方法oop

objc_msgsend 底層實現有兩種方法一中是快速查找一種是慢速查找 快速是經過彙編從響應的緩存裏面找到,慢速是經過c,c++以及彙編一塊兒完成的。佈局

之因此使用匯編的緣由是 :post

  1. c裏面不會寫一個函數保留未知的參數跳轉到任意的指> 針,c沒法實現,彙編能夠經過寄存器直接實現
  2. 快,下層編譯

快速查找

快速查找直接經過 彙編 + 緩存 來進行查找的 緩存是來自於類、優化

///ps: 類繼承於對象從這裏也能夠看出來類其實也是一個對象
struct objc_class: objc_objcet {
  // class ISA;
  Class superclass;
  cache_t cache; /// 
  classs_data_bits_t bitgs; /// 類裏面全部的數據
  
  class_rw_t *data() {
      return bits.data()
  }
}
複製代碼

類結構裏的 cacle_t 緩存 存儲方法的Selector(在iOS中SEL就是能夠根據一個SEL選擇對應的方法IMP。SEL只是描述了一個方法的格式)IMP(一個函數指針,這個被指向的函數包含一個接收消息的對象id(self 指針), 調用方法的選標 SEL (方法名),以及不定個數的方法參數,並返回一個id。也就是說 IMP 是消息最終調用的執行代碼,是方法真正的實現代碼 。咱們能夠像在C語言裏面同樣使用這個函數指針。)。IMP和Selector會組成一張哈希表,經過哈希直接查找很是快,當查找第一個方法的時候第一步找到cache,若是裏面有他會直接返回。若是沒有會經歷一個複雜的過程(慢速查找)。找到了會在裏面存一份方便下次進行查找,此次主要介紹快速找找的過程經過OC源碼ui

剛剛的方法經過Xcode調試調試彙編頁面spa

在源碼裏搜索_objc_msgsend

先把完整的彙編源碼貼上,能夠往下看,而後在回來看

********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************

    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0

    ENTRY _objc_msgSend ///************************************** 1.進入objcmsgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START
    /// x0 recevier
    // 消息接收者  消息名稱
    cmp x0, #0 // nil check and tagged pointer check
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) //// ****************************************************2.isa 優化
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class 
LGetIsaDone: ///**************************************************** 3.isa優化完成
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached ///*******************************************4.執行 CacheLookup NORMAL

LNilOrTagged:
    b.eq    LReturnZero     // nil check

    /// tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LExtTag
    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]
    b   LGetIsaDone

LExtTag:
    // 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
    
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    ret

    END_ENTRY _objc_msgSend


    ENTRY _objc_msgLookup
    UNWIND _objc_msgLookup, NoFrame

    cmp x0, #0 // nil check and tagged pointer check
    b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class 
LLookup_GetIsaDone:
    CacheLookup LOOKUP      // returns imp

LLookup_NilOrTagged:
    b.eq    LLookup_Nil // nil check

    /// tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LLookup_ExtTag
    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]
    b   LLookup_GetIsaDone

LLookup_ExtTag: 
    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   LLookup_GetIsaDone

LLookup_Nil:
    adrp    x17, __objc_msgNil@PAGE
    add x17, x17, __objc_msgNil@PAGEOFF
    ret
    END_ENTRY _objc_msgLookup
    STATIC_ENTRY __objc_msgNil
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret
    
    END_ENTRY __objc_msgNil


    ENTRY _objc_msgSendSuper
    UNWIND _objc_msgSendSuper, NoFrame
    MESSENGER_START

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

    END_ENTRY _objc_msgSendSuper

    // no _objc_msgLookupSuper

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame
    MESSENGER_START

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
    CacheLookup NORMAL

    END_ENTRY _objc_msgSendSuper2

    
    ENTRY _objc_msgLookupSuper2
    UNWIND _objc_msgLookupSuper2, NoFrame

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
    CacheLookup LOOKUP

    END_ENTRY _objc_msgLookupSuper2


.macro MethodTableLookup
    
    // push frame
    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)]

    // receiver and selector already in x0 and x1
    mov x2, x16
    bl  __class_lookupMethodAndLoadCache3/// *********************************************6.方法爲_class_lookupMethodAndLoadCache3調用的彙編語言

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

.endmacro

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band x16 is the class to search
    
    MethodTableLookup /// ********************************************** 5.查找IMP
    br  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 x16 is the class to search
    
    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached


    STATIC_ENTRY _cache_getImp

    and x16, x0, #ISA_MASK
    CacheLookup GETIMP

LGetImpMiss:
    mov x0, #0
    ret

    END_ENTRY _cache_getImp
複製代碼
  1. LLookup_NilOrTagged///針對內存裏寄存器進行賦值處理isa優化。
  2. LGetIsaDone isa /// isa處理完畢
  3. CacheLookup Normal 調用當前imp或者發送objcmsgsend_uncache

1.CacheLookup Normal

先貼源碼

/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP
 * 
 * Locate the implementation for a selector in a class method cache.
 *
 * Takes:
 *   x1 = selector
 *   x16 = class to be searched
 *
 * Kills:
 *   x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2

.macro CacheHit /// cachehit
.if $0 == NORMAL /// normal ///call imp
    MESSENGER_END_FAST
    br  x17         // call imp
.elseif $0 == GETIMP
    mov x0, x17         // return imp
    ret
.elseif $0 == LOOKUP
    ret             // return imp via x17
.else
.abort oops
.endif
.endmacro

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

.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

.macro CacheLookup
    // x1 = SEL, x16 = isa
    ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
    and w12, w1, w11        // x12 = _cmd & mask
    add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)

    ldp x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: x12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
    b   1b          /// loop

3:  // wrap: x12 = first bucket, w11 = mask 
    add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: x12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
    b   1b          /// loop

3:  // double wrap
    JumpMiss $0
    
.endmacro
複製代碼

CacheLookup 有三種 NORMAL GETIMP LOOKUP

  1. CacheHit call imp /// 查找緩存
  2. CheckMiss - /// 找不到的處理 發送_objc_msgSend_uncached
  3. add /// 若是緩存裏沒有找到可是其餘地方找到了這時候就能夠add到緩存裏面去

CacheHit 也是一個宏,若是$0 == Normal 則進行call imp 操做這是找到了操做。若是找不到的話,則執行check miss,check miss也是一個宏 $0 == Normal 會發送 objcmsgsend_uncache,這個時候整個流程就出來了。 CacheHit的意義就是要麼查找IMP要麼發送objcmsgsenduncache方法

2. _objc_msgSend_uncache

若是走到這裏說明CacheHit並無找到對應的方法而執行了_objc_msgSend_uncache /// 沒有緩存去慢速查找imp

STATIC_ENTRY __objc_msgSend_uncached
  UNWIND __objc_msgSend_uncached, FrameWithNoSaves

  // THIS IS NOT A CALLABLE C FUNCTION
  // Out-of-band x16 is the class to search
  
  MethodTableLookup /// 重點 方法列表
  br  x17  // call imp 

  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 x16 is the class to search
  
  MethodTableLookup
  ret

  END_ENTRY __objc_msgLookup_uncached
複製代碼

MethodTableLookup 方法列表這個方法是關鍵, 由於 br x 17 是設置imp,而 MethodTableLookup 在以前調用說明他是在慢速查找。

3. MethodTableLookup

.macro MethodTableLookup
  
  // push frame
  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)]

  // receiver and selector already in x0 and x1
  mov x2, x16
  bl  __class_lookupMethodAndLoadCache3 /// 重點查找IMP

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

.endmacro
複製代碼

__class_lookupMethodAndLoadCache3 這個方法顧名思義是 查找方法列表並緩存,到了這裏了咱們發現源碼裏面並無看到這個方法的定義。由於

__class_lookupMethodAndLoadCache3 方法爲_class_lookupMethodAndLoadCache3調用的彙編語言 經過_class_lookupMethodAndLoadCache3 來到c++文件

快速查找總結

oc的方法調用本質是進行objc _ msgSend調用,而objcmsgSend進行實現的時候有兩種方式一種是快速查找一種是慢速查找。快速查找是oc先去類結構裏的cache_ t的類面去查找,裏面是由 c c++ 和彙編一塊兒完成的,採用會變得緣由是他能夠作到c語言沒法完成的緣由是c裏面不會寫一個函數保留未知的參數跳轉到任意的指針,c沒法實現,彙編能夠經過寄存器直接保留,並且速度快,進入objc_ msg_ send的時候

  1. 首先會執行LLookup_NilOrTagged對isa進行優化,優化完畢後會執行LLookup_GetIsaDone.
  2. 以後執行CacheLookup進行緩存查找,緩存查找會分三步CacheHit若是是Normal,則直接返回imp說明緩存中有並返回(br x17),若是是GETIMP,LOOKUP,則會繼續往下走。若是沒有的話則執行checkmiss,Normal LOOKUP操做 發送objc_msgSend_uncahced消息,objc_msgSend_uncahced進入該方法後說明沒有緩存進入慢速查找IMP,裏面有一個MethodTableLookup,之因此這句是關鍵代碼適應br x 17是爲imp進行設置,因此該代碼是關鍵,這方法裏面執行**__class_lookupMethodAndLoadCache3**,由於該方法是**_class_lookupMethodAndLoadCache3**調用的彙編語言因此就查找到這個方法,該方法是c++文件裏的代碼

到了這裏就已經進入到c++ 文件裏面。下篇文章具體分析慢速查找流程

ps:以上爲我的理解,若是有誤歡迎指正。一塊兒進步

相關文章
相關標籤/搜索