引
今早起床打开微信,发现知识小集推送了一篇文章《阿里、字节:一套高效的iOS面试题》,打开瞅了眼,看到第二题就给我看懵圈了,为什么要设计metaclass?在我的知识体系中关于元类的认知是类对象的isa指向元类对象,元类对象存储着类方法列表,然后就没有然后了。
带着这个疑问我边开始google了,找到一文Why is MetaClass in Objective-C?,该文很好的解释了OC面向对象能力的部分师承于Smalltalk,通过类的划分和消息传递两个亮点解释了为什么要有metaclass,但是我想仅仅通过设计层面解释恐怕打动不了面试官,如果面试官反问为什么OC要借鉴Smalltalk这门语言呢?毕竟咱对Smalltalk也不了解。
OK,既然元类的存在跟方法有关,那么我们就从方法的调用阶段入手。
源代码来自objc-750
__objc_msgSend
1 | ENTRY _objc_msgSend |
源码中给了部分注释,不愿看代码的直接看下面的流程吧
1、进入_objc_msgSend
后首先判断消息的接受者是否为nil或者是否使用了tagPointer
技术,由于本文是为了探究META-CLASS
存在的意义,所以关于tagPointer
的东西就直接忽略了。
2、根据消息接受者的isa
指针找到metaclass
(因为类方法存在元类中。如果调用的是实例方法,isa指针指向的是类对象。)
3、进入CacheLookup
流程,这一步会去寻找方法缓存,如果缓存命中则直接调用方法的实现,如果缓存不存在则进入objc_msgSend_uncached
流程。
CacheLookup
1 | /******************************************************************** |
之前的_objc_msgSend
代码中我们知道CacheLookup
走的是NORMAL流程,别的支线代码就忽略了
从上述代码中可见得知当缓存命中时会调用TailCallCachedImp
验证方法IMP的有效性并调用改方法的实现,如果缓存没有命中则进入__objc_msgSend_uncached
流程。
关于缓存是如何缓存和寻找缓存的,后续会写篇blog进行详解。
__objc_msgSend_uncached
1 | STATIC_ENTRY __objc_msgSend_uncached |
一通操作后从后面调用到了_class_lookupMethodAndLoadCache3
这个方法,该方法在objc_runtim_new.mm
文件中,终于从汇编代码中走了出来!
__class_lookupMethodAndLoadCache3
1 | IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) |
该方法会去调用lookUpImpOrForward
,由于lookUpImpOrForward
方法篇幅有点长,这里简述一下该方法的流程。
1、首先会再一次的从类中寻找需要调用方法的缓存,如果能命中缓存直接返回该方法的实现,如果不能命中则继续往下走。
2、从类的方法列表中寻找该方法,如果能从列表中找到方法则对方法进行缓存并返回该方法的实现,如果找不到该方法则继续往下走。
3、从父类的缓存寻找该方法,如果父类缓存能命中则将方法缓存至当前调用方法的类中(注意这里不是存进父类),如果缓存未命中则遍历父类的方法列表,之后操作如同第2步,未能命中则继续走第3步直到寻找到基类。
4、如果到基类依然没有找到该方法则触发动态方法解析流程。
5、还是找不到就触发消息转发流程
走到这里一套方法发送的流程就都走完了,那这跟元类的存在有啥关系?我们都知道类方法是存储在元类中的,那么可不可以把元类干掉,在类中把实例方法和类方法存在两个不同的数组中?
答:行是肯定可行的,但是在lookUpImpOrForward
执行的时候就得标注上传入的cls
到底是实例对象还是类对象,这也就意味着在查找方法的缓存时同样也需要判断cls
到底是个啥。
倘若该类存在同名的类方法和实例方法是该调用哪个方法呢?这也就意味着还得给传入的方法带上是类方法还是实例方法的标识,SEL并没有带上当前方法的类型(实例方法还是类方法),参数又多加一个,而我们现在的objc_msgSend()
只接收了(id self, SEL _cmd, …)这三种参数,第一个self就是消息的接收者,第二个就是方法,后续的…就是各式各样的参数。
通过元类就可以巧妙的解决上述的问题,让各类各司其职,实例对象就干存储属性值的事,类对象存储实例方法列表,元类对象存储类方法列表,完美的符合6大设计原则中的单一职责,而且忽略了对对象类型的判断和方法类型的判断可以大大的提升消息发送的效率,并且在不同种类的方法走的都是同一套流程,在之后的维护上也大大节约了成本。
总结
本文从OC的消息机制分析了元类存在的意义,元类的存在巧妙的简化了实例方法和类方法的调用流程,大大提升了消息发送的效率。