筆者整理了一系列有關OC的底層文章,但願能夠幫助到你。這篇文章主要講解的是方法查找原理分析。c++
2.iOS的OC對象的內存對齊性能優化
3.iOS的OC的isa的底層原理bash
5.iOS的OC的方法緩存的源碼分析併發
iOS的開發中咱們會使用類中的各類方法,在OC中對方法的調用稱爲消息的發送
。對方法函數的使用每個iOS開發者都很熟悉的,可是方法函數是怎麼在底層中是怎麼查找的就是這篇文章主要來介紹的。app
爲了方便介紹接下來的內容,建立一個macOS的項目,定義了一個TestObject
的類定義了一個testMethod
的方法,在main.m
的文件裏面實現以下代碼函數
#import <Foundation/Foundation.h>
#import "TestObject.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *objc = [[TestObject alloc] init];
[objc testMethod];
}
return 0;
}
複製代碼
而後在該項目的目錄下用終端命令clang -rewrite-objc main.m
直接編譯生成一個main.cpp
文件來查看上面代碼的底層實現,最終獲得代碼以下oop
TestObject *objc = objc_msgSend(objc_getClass("TestObject"), sel_registerName("alloc")), sel_registerName("init"));
objc_msgSend(objc, sel_registerName("testMethod"));
複製代碼
其中sel_registerName
函數至關於@selector
,在TestObject
類調用alloc
,init
和testMethod
等方法都是在底層經過objc_msgSend
來進行發送消息的,能夠看出方法的本質就是經過objc_msgSend
來發送消息的。其中objc_msgSend
有兩個參數,id
是消息的接收者,SEL
方法的編號。其中經過以前的objc4-756.2
的源碼查找到能夠知道,方法的調用在底層會分別被編譯成objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
和objc_msgSendSuper_stret
。若是調用父類的方法會編譯成帶有super
字段的函數,其中objc_msgSend_stret
是調用結構體的方法。源碼分析
經過objc4-756.2
的源碼找到objc_msgSend
的底層源碼是經過彙編的方式來寫的,接下來的源碼介紹是在arm64
的架構下的。
//objc_msgSend函數的入口
ENTRY _objc_msgSend
//objc_msgSend沒有窗口
UNWIND _objc_msgSend, NoFrame
//對比當前的p0第一位是否爲空或者是taggedPointer,若是是nil會跑到LReturnZero中,
//若是是taggedPointer會跑到LNilOrTagged
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
//在正常的狀況下不是nil不是taggedPointer,會執行到這裏
//其中p13爲isa,若是消息的接收者是對象經過isa能夠找到類,若是是類能夠找到元類
ldr p13, [x0] // p13 = isa
//這裏就去到GetClassFromIsa_p16的宏方法,將p13爲isa做爲參數
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
複製代碼
從中能夠看到,進入到objc_msgSend
彙編裏面會先判斷傳進來的接收者是否爲空和是不是taggedpointer,若是都不是就先找到isa
,經過isa
找到class
,接下來介紹GetClassFromIsa_p16
。
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
複製代碼
這部分的源碼在arm64
架構下只會走#elif __LP64__
下的,經過傳進來的isa
&ISA_MASK
獲得class
,而且以p16返回,最終仍是返回上面的objc_msgSend
外面的,會繼續執行LGetIsaDone
的CacheLookup
。
其中CacheLookup
有三種查找的方式CacheLookup NORMAL|GETIMP|LOOKUP
,NORMAL
是正常的流程,GETIMP
查找imp
,LOOKUP
方法的查找。
/********************************************************************
*
* 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
// CacheHit: x17 = cached IMP, x12 = address of cached IMP
#define CACHE (2 * __SIZEOF_POINTER__)
#define CLASS __SIZEOF_POINTER__
//這是在緩存cache_t中查找方法
.macro CacheLookup
//其中x16是找到的class,經過#CACHE獲得16個字節,從而class右移16字節獲得cache_t
//其中cache_t是一個結構體,佔16字節,bucket_t佔8個字節,mask和occupied分別佔4個字節
//並將cache_t中的buckets賦值給p10,occupied和mask賦值給p11
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
//x12是獲得的hash值
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
複製代碼
這部分的內容就是查找到cache_t
,並在cache_t
查找傳進來的方法是否在這裏,具體的cache_t
的方法緩存能夠看iOS的OC的方法緩存的源碼分析這篇文章的介紹。在1
部分的內容判斷buckect
的sel
與傳進來的cmd
是否相等,便是否有緩存過的方法,若是緩存命中CacheHit
直接返回imp
,若是沒有緩存的就去到2
部分的內容。執行CheckMiss
。
.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
複製代碼
這部分是根據以前的傳進來的$0
參數來判斷須要執行那一部分。由上面的內容可知傳進來的$0
爲NORMAL
。接下來的執行__objc_msgSend_uncached
,至此objc_msgSend
經過cache_t
來快速查找部分就結束了,接下來的部分就是經過慢速的方法查找。
objc_msgSend
經過第2部分的cache_t
快速查找,在緩存中找不到有緩存的方法,此時就須要進行沒有緩存的慢速查找。
這部分的內容就是
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
.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)]
// receiver and selector already in x0 and x1
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
AuthenticateLR
.endmacro
複製代碼
經過以前的文章iOS的OC源碼分析之類的結構分析能夠知道方法的是存類的bits
的ro
和rw
裏面的methodList
的,在cache_t
裏面找不到方法的時候,此時就須要在methodList
找了,而MethodTableLookup
就是爲了這部份內容作的準備。最終會執行到__class_lookupMethodAndLoadCache3
這個函數。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
傳進來的obj
是對象,sel
是方法的編號,cls
是類。而後直接調用lookUpImpOrForward
函數,此時進來的參數中initialize
是YES,cache
是NO,resolver
是YES,由於此時是在cache_t
緩存中找不到方法才執行到這裏的。
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 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.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// 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.assertLocked(); // Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. { 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. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; } 複製代碼
這個lookUpImpOrForward
函數的代碼有點多,就分開一點點地分析。其中runtimeLock
是防止線程併發競爭的鎖。
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
複製代碼
這裏是再次判斷若是有緩存的,直接在緩存中找到imp
返回出去。
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// 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 } 複製代碼
上面這部分的內容是判斷類是不是合法的,而且判斷類是不是初始化了,若是沒有初始化好的話,就須要進入到realizeClass
函數裏面進行初始化,這個函數也是對當前的類的父類和元類都作了初始化,這部分的內容就是爲了接下來的類在bits
裏面查找方法作好準備的。
// Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); assert(cls->isRealized()); // fixme nil cls? // fixme nil sel? for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) { method_t *m = search_method_list(*mlists, sel); if (m) return m; } return nil; } static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif cache_fill (cls, sel, imp, receiver); } 複製代碼
這部分的內容是在類class
裏面查找方法。getMethodNoSuper_nolock
函數是在類cls
的data()裏面的methodList
列表循環查找sel
。若是找到就返回method_t
。而且執行log_and_fill_cache
函數,到最後仍是會執行cache_fill
。此時會將方法再次緩存在cache_t
中。
// 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; } } } 複製代碼
這部分的內容是在類裏面查找不到方法了,須要去父類查找方法。由於咱們以前的查找都是對當前的類開啓objc_msgSend
彙編查找和cls的bits的methodList查找的,父類的方法也是可能有緩存的,因此此時經過父類的循環首先是經過cache_getImp
函數來查找imp
。其中_objc_msgForward_impcache
是實際存儲在其中的函數指針方法緩存。若是有直接執行log_and_fill_cache
函數直接done
,若是沒有就break
出去。若是沒有找到imp
或者找到imp
作轉發了此時不緩存,會直接調用getMethodNoSuper_nolock
函數來查找。若是找到仍是會對這個方法作緩存的。
上面的介紹都是方法存在的,若是在方法查找的過程當中,查找不到的話是會報異常的,例如執行以下的代碼
TestObject *testObject = [[TestObject alloc] init];
[testObject performSelector:@selector(testErrorMthod)];
複製代碼
// 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. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); 複製代碼
最終會執行_objc_msgForward_impcache
函數,而_objc_msgForward_impcache
是彙編的
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
複製代碼
由此可知,__objc_msgForward_impcache
會執行到__objc_msgForward
,最終會執行到__objc_forward_handler
。經過源碼的查找,最後會執行objc_defaultForwardHandler
函數打印出錯誤的信息。
#if !__OBJC2__
// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret
objc_defaultForwardStretHandler(id self, SEL sel)
{
objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif
複製代碼
objc_msgSend
的底層是用匯編來寫的呢?方法的查找是在objc_msgSend
的函數下進行的,這一個過程有快速查找和慢速查找。
objc_msgSend
快速查找,而objc_msgSend
是在彙編的狀況下進行的。進入objc_msgSend
先判斷第一位的內存值是否爲空或者是taggedPointer
,若是是就走相應的流程。若是不是就是正常的流程就須要經過GetClassFromIsa_p16
找到isa
,經過isa
執行CacheLookup
去到類的cache_t
來查找是否緩存方法,若是沒有就執行__objc_msgSend_uncached
。此時就至關於快速查找方法是找不到了,須要過分到慢速的查找。__objc_msgSend_uncached
能夠執行MethodTableLookup
函數來爲接下來須要在類的bits中查找的方法做準備。最終會在彙編中過渡到c++函數,執行class_lookupMethodAndLoadCache3
。經過lookUpImpOrForward
函數來分別遍歷類和父類的方法列表中查找,若是找到就緩存在cache_t
中。若是沒有找到,而且沒有作消息轉發
的操做,最終會執行_objc_msgForward_impcache
而後進去__objc_msgForward
的__objc_forward_handler
函數報錯。至此,方法的查找底層原理就介紹完畢了。