1. 方法:
方法缓存是在cache_t,cache读取流程,即objc_msgSend 和 cache_getImp
2. 运行时:
是什么:Runtime是一套API,由c、c++、汇编一起写成的,为OC提供了运行时.
Runtime版本Runtime有两个版本——Legacy和Modern,
Legacy(``-old、__OBJC__), Modern(-new、__OBJC2__)
Runtime调用方式:
Runtime API,如sel_registerName(),class_getInstanceSizeNSObject API,如isKindOf()OC上层方式,如@selector()
3. 方法的本质是通过objc_msgSend发送消息,id是消息接收者,SEL是方法编号.
注意:如果外部定义了C函数并调用如void sayHello() {},在clang编译之后还是sayHello()而不是通过objc_msgSend去调用.因为发送消息就是找函数实现的过程,而C函数可以通过函数名——指针就可以找到.
实现上图, 这其中需要注意两点:
-
1、直接调用
objc_msgSend,需要导入头文件#import <objc/message.h> -
2、需要将
target --> Build Setting -->搜索msg -- 将enable strict checking of obc_msgSend calls由YES 改为NO,将严厉的检查机制关掉,否则objc_msgSend的参数会报错
2. 方法调用,首先是在类中查找,如果类中没有找到,会到类的父类中查找.
二、消息查找流程
消息查找流程其实是通过上层的方法编号sel发送消息objc_msgSend找到具体实现imp的过程
objc_msgSend是用汇编写成的,至于为什么不用C而是用汇编写,是因为:
C语言不能通过写一个函数,保留未知的参数,跳转到任意的指针,而汇编有寄存器- 对于一些调用频率太高的函数或操作,使用汇编来实现能够提高效率和性能,容易被机器来识别
总结:
-
对于
对象方法(即实例方法),即在类中查找,其慢速查找的父类链是:类--父类--根类--nil -
对于
类方法,即在元类中查找,其慢速查找的父类链是:元类--根元类--根类--nil -
如果
快速查找、慢速查找也没有找到方法实现,则尝试动态方法决议 -
如果
动态方法决议仍然没有找到,则进行消息转发
动态方法决议
- 判断
类是否是元类- 如果是
类,执行实例方法的动态方法决议resolveInstanceMethod - 如果是
元类,执行类方法的动态方法决议resolveClassMethod,如果在元类中没有找到或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod中查找,主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议
- 如果是
- 如果
动态方法决议中,将其实现指向了其他方法,则继续查找指定的imp,即继续慢速查找lookUpImpOrForward流程
④ 优化方案
上面的这种方式是单独在每个类中重写,有没有更好的,一劳永逸的方法呢?其实通过方法慢速查找流程可以发现其查找路径有两条
- 实例方法:类 -- 父类 -- 根类 -- nil
- 类方法:元类 -- 根元类 -- 根类 -- nil
它们的共同点是如果前面没找到,都会来到根类即NSObject中查找,所以我们是否可以将上述的两个方法统一整合在一起呢?答案是可以的,可以通过NSObject添加分类的方式来实现统一处理,而且由于类方法的查找,在其继承链,查找的也是实例方法,所以可以将实例方法 和 类方法的统一处理放在resolveInstanceMethod方法中,如下所示
当然,上面这种写法还是会有其他的问题,比如系统方法也会被更改,针对这一点,是可以优化的,即我们可以针对自定义类中方法统一方法名的前缀,根据前缀来判断是否是自定义方法,然后统一处理自定义方法,例如可以在崩溃前pop到首页,主要是用于app线上防崩溃的处理,提升用户的体验.
那么把所有崩溃都在NSObjct分类中处理,加以前缀区分业务逻辑,岂不是美滋滋?错!
- 统一处理起来耦合度高
- 逻辑判断多
- 可能在
NSObjct分类动态方法决议之前已经做了处理 SDK封装的时候需要给一个容错空间
因此前面的 ④ 优化方案 也不是一个最完美的解决方案.那么,这也不行,那也不行,那该怎么办?放心,苹果爸爸已经给我们准备好后路了!
3. 消息转发机制
msgSends开头的日志文件,打开发现在崩溃前,执行了以下方法
-
两次动态方法决议:
resolveInstanceMethod方法 -
两次消息快速转发:
forwardingTargetForSelector方法 -
两次消息慢速转发:
methodSignatureForSelector + resolveInvocation
快速转发流程解决崩溃 - forwardingTargetForSelector
如下代码就是通过快速转发解决崩溃——即TCJPerson实现不了的方法,转发给TCJStudent去实现(转发给已经实现该方法的对象)
也可以直接不指定消息接收者,直接调用父类的该方法,如果还是没有找到,则直接报错
慢速转发流程解决崩溃
慢速转发流程就是先methodSignatureForSelector提供一个方法签名,然后forwardInvocation通过对NSInvocation来实现消息的转发
其实也可以对forwardInvocation方法中的invocation不进行处理,也不会崩溃报错
总结:
到目前为止,objc_msgSend发送消息的流程就分析完成了,在这里简单总结下
【快速查找流程】首先,在类的缓存cache中查找指定方法的实现【慢速查找流程】如果缓存中没有找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找【动态方法决议】如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod方法【消息转发】如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发- 如果转发之后也没有,则程序直接报错崩溃
unrecognized selector sent to instance