Objective-C 是一個動態語言,這意味着它不只須要一個編譯器,也須要一個運行時系統來動態得建立類和對象、進行消息傳遞和轉發。理解 Objective-C 的 Runtime 機制能夠幫咱們更好的瞭解這個語言,適當的時候還能對語言進行擴展,從系統層面解決項目中的一些設計或技術問題。瞭解 Runtime ,要先了解它的核心 - 消息傳遞 (Messaging)。c++
Runtime
基本是用 C 和彙編寫的,可見蘋果爲了動態系統的高效而做出的努力。你能夠在這裏 密碼:tuw8 下到蘋果維護的開源代碼。蘋果和 GNU 各自維護一個開源的 runtime 版本,這兩個版本之間都在努力的保持一致。緩存
一個對象的方法像這樣[obj foo],經過 clang -rewrite-objc
命令查看編譯後的代碼(因爲以前的文章操做過,這裏不詳細解釋操做流程了),編譯器轉成消息發送objc_msgSend(obj, foo)bash
objc_msgsend
底層有兩種查找方式:架構
爲何要使用匯編?函數
先在剛剛提供的objc源碼裏查找objc_msgSend
,找到咱們經常使用的架構arm64
彙編文件,能夠看到ENTRY _objc_msgSend
,其實就是這個函數的入口 oop
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:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#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
複製代碼
可能看不懂彙編,根據註釋大概推測其意思,下面的代碼主要作了非空檢查和無標記指針檢查(若是指針小於等於 LNilOrTagged 直接return返回)post
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
複製代碼
接下來看以下代碼,根據isa獲取這個類,LGetIsaDone
是表示isa處理完畢,CacheLookup NORMAL
表示直接調用當前的imp
或者發送objc_msgSend_uncached
無緩存消息ui
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
複製代碼
在當前文件裏搜索CacheLookup
,來到它的宏定義this
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
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
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// 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
3: // double wrap
JumpMiss $0
.endmacro
複製代碼
在CacheLookup宏定義這裏spa
CacheHit
: 緩存命中,方法的實現IMP在寄存器中,而後傳遞出去CheckMiss
: 緩存沒命中,發送_objc_msgSend_uncachedadd
: 若是緩存裏沒找到,去其餘地方查找到該方法實現後添加到緩存
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
複製代碼
看到CacheHit
的宏定義: 在上面調用的時候,傳遞過來的是NORMAL
,執行了TailCallCachedImp
,即若是緩存命中的話,則返回緩存裏的IMP
.
.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
複製代碼
再看到CheckMiss
的宏定義:在上面調用的是NORMAL
,因此這裏會發送__objc_msgSend_uncached
的消息
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
,因此繼續跟進查看
.macro MethodTableLookup
// 省略
// 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
//省略
.endmacro
複製代碼
前面後面的彙編代碼只能看到有作字節對齊的操做,不過因爲不懂彙編,具體作什麼不是很清楚,不過看到了跳轉進了一個很是重要的函數__class_lookupMethodAndLoadCache3
繼續搜索__class_lookupMethodAndLoadCache3
發現並不能在當前彙編文件裏找到聲明,這時猜測會不會是跳轉到了代碼裏
因而全局搜索class_lookupMethodAndLoadCache3
,果真在objc-runtime-new.mm
文件裏找到了它的函數實現
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
因此這裏經過調用lookUpImpOrForward
開啓了慢速查找
的過程
來到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
// 查找緩存!!!
if (cache) {
//彙編代碼的方式實現的!
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
//先省略
}
複製代碼
initialize
爲YES,cache
爲NO,resolver
爲YESLGetIsaDone
這個判斷是在isa處理完畢後才走緩存查找的彙編代碼的,因此這個類是加載解析好的,即initialize
和resolver
都爲YES,cache
爲NO是由於在彙編裏快速查找沒有找到方法緩存纔會執行到這裏,因此這裏確定爲NOcache_getImp
去獲取imp,因爲傳遞進來的cache爲NO,因此這裏不會執行// Optimistic cache lookup
// 查找緩存!!!
if (cache) {
//彙編代碼的方式實現的!
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//彙編代碼(在以前上面的彙編文件裏)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP
複製代碼
checkIsKnownClass(cls);
檢查這個類是否已知,若是未知則拋出異常realizeClass(cls)
和初始化方法_class_initialize (_class_getNonMetaClass(cls, inst));
在lookUpImpOrForward
裏還有retry相關的代碼,繼續分析
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//Method(SEL IMP)
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.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 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.
//_objc_msgForward
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
複製代碼
第一步看到這裏再一次調用了cache_getImp(cls, sel);
去從緩存中獲取imp,傳遞進來明明已經知道是NO了,爲何再去查找一次呢?
在objc_init
的時候有一個函數remap(cls)
,在彙編最開始查找該方法的時候若是沒有方法緩存,但可能會在這個類初始化方法objc_init
的過程當中,對這個類進行了重映射remap
,即把該方法添加到方法緩存裏了,因此這裏要再去查找一次cache
有緩存就可能會節省不少時間
接下來先從當前類的方法列表method_list
去找,找到了就log_and_fill_cache
打印日誌並把方法添加到緩存中
若是沒找到則繼續找父類的緩存cache_getImp(curClass, sel)
,再找父類的方法列表,和以前在本類的查找順序同樣,找到了也是添加到方法緩存log_and_fill_cache
動態方法解析和消息轉發
,篇幅緣由,接下來的這個過程在Runtime底層原理(二)動態方法解析和消息轉發以上均爲我的探索源碼的理解和所得,若有錯誤請指正,歡迎討論。