OC是一門動態語言全部的方法都是由運行時進行。 Objective-C 語言將決定儘量的從編譯和連接時推遲到運行時。只要有可能,Objective-C 老是使用動態 的方式來解決問題。這意味着 Objective-C 語言不只須要一個編譯器,同時也須要一個運行時系統來執行 編譯好的代碼。這兒的運行時系統扮演的角色相似於 Objective-C 語言的操做系統,Objective-C 基於該系統來工做。 Runtime的做用是 能動態產生/修改一個類,一個成員變量,一個方法c++
Runtime調用有三種方式緩存
咱們知道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
- c裏面不會寫一個函數保留未知的參數跳轉到任意的指> 針,c沒法實現,彙編能夠經過寄存器直接實現
- 快,下層編譯
快速查找直接經過 彙編 + 緩存 來進行查找的 緩存是來自於類、優化
///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
複製代碼
先貼源碼
/********************************************************************
*
* 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
CacheHit 也是一個宏,若是$0 == Normal 則進行call imp 操做這是找到了操做。若是找不到的話,則執行check miss,check miss也是一個宏 $0 == Normal 會發送 objcmsgsend_uncache,這個時候整個流程就出來了。 CacheHit的意義就是要麼查找IMP要麼發送objcmsgsenduncache方法
若是走到這裏說明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 在以前調用說明他是在慢速查找。
.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的時候
到了這裏就已經進入到c++ 文件裏面。下篇文章具體分析慢速查找流程。
ps:以上爲我的理解,若是有誤歡迎指正。一塊兒進步