原文连接面试
今早起床打开微信,发现知识小集推送了一篇文章《阿里、字节:一套高效的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的消息机制分析了元类存在的意义,元类的存在巧妙的简化了实例方法和类方法的调用流程,大大提高了消息发送的效率。