原文連接面試
今早起牀打開微信,發現知識小集推送了一篇文章《阿里、字節:一套高效的iOS面試題》,打開瞅了眼,看到第二題就給我看懵圈了,爲何要設計metaclass?在個人知識體系中關於元類的認知是類對象的isa指向元類對象,元類對象存儲着類方法列表,而後就沒有而後了。數組
帶着這個疑問我邊開始google了,找到一文Why is MetaClass in Objective-C?,該文很好的解釋了OC面向對象能力的部分師承於Smalltalk,經過類的劃分和消息傳遞兩個亮點解釋了爲何要有metaclass,可是我想僅僅經過設計層面解釋恐怕打動不了面試官,若是面試官反問爲何OC要借鑑Smalltalk這門語言呢?畢竟咱對Smalltalk也不瞭解。緩存
OK,既然元類的存在跟方法有關,那麼咱們就從方法的調用階段入手。bash
源代碼來自objc-750
微信
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
複製代碼
源碼中給了部分註釋,不肯看代碼的直接看下面的流程吧 一、進入_objc_msgSend
後首先判斷消息的接受者是否爲nil或者是否使用了tagPointer
技術,因爲本文是爲了探究META-CLASS
存在的意義,因此關於tagPointer
的東西就直接忽略了。 二、根據消息接受者的isa
指針找到metaclass
(由於類方法存在元類中。若是調用的是實例方法,isa指針指向的是類對象。) 三、進入CacheLookup
流程,這一步會去尋找方法緩存,若是緩存命中則直接調用方法的實現,若是緩存不存在則進入objc_msgSend_uncached
流程。oop
/********************************************************************
*
* 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
.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
.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
.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
// 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
複製代碼
以前的_objc_msgSend
代碼中咱們知道CacheLookup
走的是NORMAL流程,別的支線代碼就忽略了 從上述代碼中可見得知當緩存命中時會調用TailCallCachedImp
驗證方法IMP的有效性並調用改方法的實現,若是緩存沒有命中則進入__objc_msgSend_uncached
流程。ui
關於緩存是如何緩存和尋找緩存的,後續會寫篇blog進行詳解。google
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
*這裏忽略寄存器的操做*
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3
*這裏忽略寄存器的操做*
.endmacro
複製代碼
一通操做後從後面調用到了_class_lookupMethodAndLoadCache3
這個方法,該方法在objc_runtim_new.mm
文件中,終於從彙編代碼中走了出來!spa
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
該方法會去調用lookUpImpOrForward
,因爲lookUpImpOrForward
方法篇幅有點長,這裏簡述一下該方法的流程。debug
一、首先會再一次的從類中尋找須要調用方法的緩存,若是能命中緩存直接返回該方法的實現,若是不能命中則繼續往下走。
二、從類的方法列表中尋找該方法,若是能從列表中找到方法則對方法進行緩存並返回該方法的實現,若是找不到該方法則繼續往下走。
三、從父類的緩存尋找該方法,若是父類緩存能命中則將方法緩存至當前調用方法的類中(注意這裏不是存進父類),若是緩存未命中則遍歷父類的方法列表,以後操做如同第2步,未能命中則繼續走第3步直到尋找到基類。
四、若是到基類依然沒有找到該方法則觸發動態方法解析流程。
五、仍是找不到就觸發消息轉發流程
走到這裏一套方法發送的流程就都走完了,那這跟元類的存在有啥關係?咱們都知道類方法是存儲在元類中的,那麼可不能夠把元類幹掉,在類中把實例方法和類方法存在兩個不一樣的數組中?
答:行是確定可行的,可是在lookUpImpOrForward
執行的時候就得標註上傳入的cls
究竟是實例對象仍是類對象,這也就意味着在查找方法的緩存時一樣也須要判斷cls
究竟是個啥。
假若該類存在同名的類方法和實例方法是該調用哪一個方法呢?這也就意味着還得給傳入的方法帶上是類方法仍是實例方法的標識,SEL並無帶上當前方法的類型(實例方法仍是類方法),參數又多加一個,而咱們如今的objc_msgSend()
只接收了(id self, SEL _cmd, ...)這三種參數,第一個self就是消息的接收者,第二個就是方法,後續的...就是各式各樣的參數。
經過元類就能夠巧妙的解決上述的問題,讓各種各司其職,實例對象就幹存儲屬性值的事,類對象存儲實例方法列表,元類對象存儲類方法列表,完美的符合6大設計原則中的單一職責,並且忽略了對對象類型的判斷和方法類型的判斷能夠大大的提高消息發送的效率,而且在不一樣種類的方法走的都是同一套流程,在以後的維護上也大大節約了成本。
本文從OC的消息機制分析了元類存在的意義,元類的存在巧妙的簡化了實例方法和類方法的調用流程,大大提高了消息發送的效率。