iOS底层探索之Runtime: 动态方法解析
Posted 卡卡西Sensei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层探索之Runtime: 动态方法解析相关的知识,希望对你有一定的参考价值。
1. 回顾
ios底层探索之Runtime(一):运行时&方法的本质
iOS底层探索之Runtime(二): objc_msgSend&汇编快速查找分析
iOS底层探索之Runtime(三): lookUpImpOrForward慢速查找分析
在上一篇博文中,介绍了Runtime
的慢速查找流程lookUpImpOrForward
,本章内容主要分析动态方法解析
流程。
在缓存中、自己的
class_rw_t
中、父类的cache
中、父类的class_rw_t
中都没有找到imp
,就会进入objc_msgSend
的第二个阶段动态方法解析
。
2. 动态方法解析
2.1 resolveMethod_locked
从源码中可以发现,在lookUpImpOrForward
里面循环遍历没有找到imp
之后就会进入下面这个判断,注释也写的很明显了。
// No implementation found. Try method resolver once.
//behavior = 3 , LOOKUP_RESOLVER = 2
// 3 & 2 = 2,就是找两个的相同值
//0011
//0010
//0010 -> 2
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;//异或:相同为0,不同为1
// 3 ^= 2 -> 1 ,behavior=1,下次再进来就是behavior & LOOKUP_RESOLVER -> 相当于1 & 2=0,为0这个方法也就只执行一次,相当于单例的作用
return resolveMethod_locked(inst, sel, cls, behavior);
}
- resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
从resolveMethod_locked
方法里面可以看出,主要是判断是不是元类,非元类和元类两种情况,分情况进行处理。
- 非元类:调用
resolveInstanceMethod:
- 元类:调用
resolveClassMethod:
在正式开始分析之前,我们先看看下面这个例子
在JPStudent
的父类JPPerson
类里面,声明了一个方法jp_sayHello
,但是没有实现,那么JPStudent
子类调用了父类的方法,会出现什么情况呢?
JPPerson *jp = [[JPPerson alloc]init];
[jp jp_sayHello]
很显然程序是会奔溃的,奔溃信息如下:
[9441:373947] -[JPStudent jp_sayHello]: unrecognized selector sent to instance 0x100709660
[9441:373947] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JPStudent jp_sayHello]: unrecognized selector sent to instance 0x100709660'
那么为什么会报这个unrecognized selector sent to instance
经典的错误呢?我们从源码来分析
在lookUpImpOrForward
方法里面,如果所有的父类里面没有找到imp
,就会给imp
赋值forward_imp
,break
跳出,最后返回imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
.....代码省略......
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
.....代码省略......
return imp;
2.2 _objc_msgForward_impcache
- _objc_msgForward_impcache
那么赋值的这个imp
,也就是_objc_msgForward_impcache
,到底是个什么imp
呢?
在源码里面全局搜索objc_msgForward_impcache
发现
objc_msgForward_impcache
只有一行代码
然后再继续搜索__objc_msgForward
__objc_msgForward
里面又调用了TailCallFunctionPointer
然后再搜索TailCallFunctionPointer
.macro TailCallFunctionPointer
// $0 = function pointer value
braaz $0
.endmacro
意思是要跳转
$0
,那么得先看x17
,x17
就是__objc_forward_handler
那么再全局搜索
__objc_forward_handler
,发现没有找到,那么再去掉下划线搜索
我的天哪!原来报错信息是在这里打印的啊!这一波操作
666
啊!
_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);
2.3 知识点补充
OC
底层是不分实例方法(-
)和对象方法(+
)的,底层会自动给你加上-
号和+
号,然后按格式打印报错信息。还记得isa
走位图吗?iOS底层探索之类的结构(上):ISA
从图中,可以知道,根元类的superClass
指向了根类,我相信大家都有疑问?
- 类方法,存在元类里面,实例方法存在类里面。
- 当调用类方法,会沿着继承链,往上找,一直找到元类都没有找到类方法,就回会去根类里面找,根类里面发现有个同名的实例方法,就会直接调用。
- 如果根类里面没有同名的实例方法就会报错
unrecognized selector sent to instance
这就解释了OC
其实是不分+
方法和-
方法的,对于底层来说,都是消息发送,sel
都一样。所以会有根元类的superClass
指向了根类这么一个指针的指向。
那么如果方法没有实现,就只能程序崩溃,报错了吗?有没有什么补救措施,好让我拯救地球呢!
有的靓仔,苹果工程师会满足你的!请继续往下看!
2.4 resolveInstanceMethod
在JPPerson
类的.m
文件下,实现resolveInstanceMethod
方法
程序运行起来报错了,但是我们发现,在报错奔溃之前
走了resolveInstanceMethod
方法,那么也就是说,我们可以提前处理,不让程序奔溃。
@implementation JPPerson
- (void)jp_sayNB {
NSLog(@"%@,%s",self,__func__);
}
+(BOOL)resolveInstanceMethod:(SEL)sel {
// 方法匹配
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"jp_sayHello"]) {
IMP sayNBImp = class_getMethodImplementation(self, @selector(jp_sayNB));
Method method = class_getClassMethod(self, @selector(jp_sayNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayNBImp, type);//添加方法
}
return [super resolveInstanceMethod:sel];
}
程序跑起来,看看结果如何
程序并没有奔溃,而是走了我们添加的方法里面去,这就是对象方法的动态解析。
2.5 resolveClassMethod
那么我们再来看看,类方法的动态解析。
从图中可以看出,在调用了resolveClassMethod
方法,也调用了resolveInstanceMethod
方法,这是怎么回事呢?
从底层代码来看,处理流程差不多,那么我们现在去实现类方法的动态解析。
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
class_getClassMethod
方法,是获取元类的实例方法。在类里面实现resolveClassMethod
,相当于元类里面以对象方式存在
JPPerson
类调用没有实现方法[JPPerson jp_sayHappy]
,在JPPerson
中实现resolveClassMethod
方法解析动态解析
+ (void)jp_say666 {
NSLog(@"%@,%s",self,__func__);
}
// 相当于 元类中的对象方法
+(BOOL)resolveClassMethod:(SEL)sel {
//获取元类的对象方法
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"jp_sayHappy"]) {
IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("JPPerson"), @selector(jp_say666));
Method method = class_getClassMethod(objc_getMetaClass("JPPerson"), @selector(jp_say666));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("JPPerson"), sel, sayNBImp, type);//添加方法
}
return [super resolveClassMethod:sel];
}
动态方法解析,么得问题,
jp_sayHappy
方法并没有实现,但是在动态方法解析的时候,可以添加jp_say666
方法来防止崩溃。上面提到了,resolveInstanceMethod
方法还走了一次,这是因为类方法,存在元类里面,但是同时也可以以对象方法的形式存在,就有两条路径,上面isa
的走位图,也验证了这个说法,其实也可以解释底层是不分+/-
方法的。
但是又暴露出了一个问题,就是每个类都得写一次resolveInstanceMethod
和resolveClassMethod
,那要是有多个类呢?都写一遍就太麻烦了。那么给NSObject
写一个分类NSObject+JPResolver
就可以很好的解决这种问题。
+(BOOL)resolveInstanceMethod:(SEL)sel {
// 方法匹配
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"jp_sayHello"]) {
IMP sayNBImp = class_getMethodImplementation(self, @selector(jp_sayNB));
Method method = class_getClassMethod(self, @selector(jp_sayNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayNBImp, type);//添加方法
}else if ([methodName isEqualToString:@"jp_sayHappy"]) {
IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("JPPerson"), @selector(jp_say666));
Method method = class_getClassMethod(objc_getMetaClass("JPPerson"), @selector(jp_say666));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("JPPerson"), sel, sayNBImp, type);//添加方法
}
return NO;
}
那要是上面两种情况,程序员都不处理呢?
苹果:给你机会,你得珍惜啊!你不处理,我也不能崩溃啊!那就再来一次吧!
那么再来一次是什么机会呢?请听下回分解
iOS底层探索之Runtime(五): 消息转发
3.总结
- 在
lookUpImpOrForward
慢速查找imp
没有找到会进入动态方法解析流程 resolveInstanceMethod
实例方法解析,可以动态添加方法,防止崩溃。resolveClassMethod
类方法解析,也会调用resolveInstanceMethod
,根据isa
走位图可以知道,类方法是存在元类里面,但是也会以对象方法形式存在在类中,主要是因为底层不区分+
和-
。
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得学习到了的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
以上是关于iOS底层探索之Runtime: 动态方法解析的主要内容,如果未能解决你的问题,请参考以下文章
iOS底层探索之Runtime: lookUpImpOrForward慢速查找分析
iOS底层探索之Runtime: objc_msgSend&汇编快速查找分析
iOS之深入解析类方法+load与+initialize的底层原理