iOS進階—Runtime原始碼解析:訊息傳送
阿新 • • 發佈:2019-01-02
GitHub參考
PS:參考GitHub分享的objc-runtime-master
程式碼
1、OC中的方法呼叫,實際上objc_msgSend
函式呼叫
2、objc_msgSend
的執行過程大致可以分為三個部分:
- 訊息傳送
- 動態方法解析
- 訊息轉發
RunTime是開源的,所以我們可以方便的檢視到RunTime的原始碼(可參考Github),底層主要運用的語言包含組合語言,C/C++
訊息傳送
objc_msgSend
的相關程式碼如下:
針對不同的CPU平臺,會有不同的組合語言,所以我們只關注arm64
(真機)環境下的
主要看從ENTRY _objc_msgSend
到END_ENTRY _objc_msgSend
組合語言如下:我們簡單的看下
ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame MESSENGER_START /// x0 recevier // cmp對比的意思 訊息接收者->x0 cmp x0, #0 // nil check and tagged pointer check 檢查是否為空,如果是,直接teturn //b->跳轉 le->小於等於 b.le LNilOrTagged // (MSB tagged pointer looks negative) 跳轉到下面的LNilOrTagged //如果不為空,進入這裡 得到isa指標,x13 = isa, ldr x13, [x0] // x13 = isa //通過與操作,得到類 x16 = class and x16, x13, #ISA_MASK // x16 = class //順序執行到下面 LGetIsaDone: //字面意思是在快取當中查詢,如果快取裡面存在,呼叫imp,否則 objc_msgSend_uncached 呼叫快取中不存在的方法 /* CacheLookup 其實是定義的一個巨集,類似於 #define CacheLookup 檢視 */ CacheLookup NORMAL // calls imp or objc_msgSend_uncached LNilOrTagged: b.eq LReturnZero // nil check // tagged mov x10, #0xf000000000000000 cmp x0, x10 b.hs LExtTag adrp x10,
[email protected] add x10, x10, [email protected] ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] b LGetIsaDone LExtTag: // ext tagged adrp x10, [email protected] add x10, x10, [email protected] 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 //相等於return END_ENTRY _objc_msgSend
定義CacheLookup的彙編程式碼部分,有兩種查詢結果,找到了和找不到,找到了->呼叫CacheHit
,如果快取中找不到就呼叫CheckMiss
.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
CheckMiss相關程式碼,如果我們的狀態屬於正常狀態NORMAL
,則呼叫__objc_msgSend_uncached
.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
__objc_msgSend_uncached以後的相關程式碼
//入口
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
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
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
//接收器和選擇器已經在x0和x1中,跳轉 呼叫 __class_lookupMethodAndLoadCache3
mov x2, x16
bl __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
__class_lookupMethodAndLoadCache3
就是C/C++實現的,彙編過程結束
C/C++與組合語言有一個約定,C/C++中的方法會在彙編中多一個下劃線,所以我們接下來搜尋_class_lookupMethodAndLoadCache3
我們檢視新版本
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
//cls 類 sel 方法名 obj 物件 YES 是否初始化 NO 是否在快取中 YES 是否嘗試動態解析
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
lookUpImpOrForward方法
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
//在彙編程式中,已經查詢過快取了,在這裡仍要查詢的原因是因為,OC是動態呼叫方法,防止動態呼叫時直接呼叫此方法
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//在此類方法列表中查詢,如果查詢不到就去七父類方法中查詢,直到找到為止,執行goto done;
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
官方大致圖解如下: