前言
在上一篇底层原理之方法慢速查找流程我们探索了消息发送objc_msgSend通过汇编快速查找IMP,如果找不到IMP会进入lookUpImpOrForward慢速查找流程。慢速查找是一个递归耗时的过程,如果找到IMP就返回给汇编执行并且插入到类的cache中,如果找不到IMP系统会执行resolveMethod_locked动态方法决议,下面就看一下动态方法决议干了什么?附oc源码。
resolveMethod_locked
IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//判断是cls是否是元类,调用对象方法 cls是类非元类
if (! cls->isMetaClass()) {
resolveInstanceMethod(inst, sel, cls);
}
else {
//调用类方法 inst是类 cls是元类
resolveClassMethod(inst, sel, cls);
//如果resolveClassMethod中没有IMP,就在resolveInstanceMethod再查找一次
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
//再次快速、慢速查找一次imp,因为动态方法决议可能实现了IMP
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
分析:resolveMethod_locked主要分为对象方法动态方法决议和类方法动态方法决议,如果调用对象方法就进入resolveInstanceMethod,如果调用类方法就进入resolveClassMethod。注意一个坑点:如果resolveClassMethod中没有IMP,就在resolveInstanceMethod再查找一次,下面会分析原因。
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//系统在NSObject中默认实现了resolveInstanceMethod方法,所以此处不会为空
//系统默认实现这个方法目的是容错,防止程序员没有实现而导致不稳定
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
return;
}
//首先inst是对象,cls是类
//给cls类发送消息,查看resolve_sel中是否实现了sel
//cls类是是消息接收者,所以resolve_sel是类方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//如果resolve_sel实现了sel的IMP,那么快速、慢速查找一遍缓存返回IMP
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
//.....
}
else {
//...
}
}
}
分析:根据上面的注释分析一下要点
系统在根类NSObject中默认实现了resolveInstanceMethod方法,目的是容错,防止程序员没实现导致系统不稳定,苹果爸爸还是很细的- cls类是是消息接收者,所以
resolveInstanceMethod是类方法 - resolveInstanceMethod中如果实现了sel的IMP,那么
快速、慢速查找一遍缓存返回IMP
lookUpImpOrNilTryCache
lookUpImpOrNilTryCache内部是调用了lookUpImpTryCache
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//是否初始化了类cls,如果没有就走一遍慢速查找流程
if (slowpath(!cls->isInitialized())) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//从缓存中获取IMP
IMP imp = cache_getImp(cls, sel);
//如果imp不为空进入done
//缓存查找imp可能为_objc_msgForward_impcache即找不到IMP
if (imp != NULL) goto done;
//共享缓存中查找
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
//如果为空进入慢速查找
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
分析:根据上面的注释分析要点
- 如果
resolveInstanceMethod中实现了IMP,会加载进缓存。lookUpImpTryCache的目的就是优先从缓存中快速查找IMP,如果找不到再慢速查找
demo实现对象方法动态方法决议
通过上面的分析,我们用代码实现一下resolveInstanceMethod,看看是否可以防止返回_objc_msgForward_impcache而导致的奔溃。初始化LGPerson对象,调用不存在的对象方法sayHello()
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"----进入动态方法决议----");
if([NSStringFromSelector(sel) isEqualToString:@"sayHello"]){
IMP selimp=class_getMethodImplementation(self, @selector(wgy_sayHello));
Method selmethod=class_getInstanceMethod(self, @selector(wgy_sayHello));
const char * type=method_getTypeEncoding(selmethod);
return class_addMethod(self, sel, selimp, type);
}
return [super resolveInstanceMethod:sel];
}
-(void)wgy_sayHello{
NSLog(@"%s",__func__);
}
输出:
----进入动态方法决议----
[LGPerson wgy_sayHello]
lldb断点调试一下
分析:
resolveInstanceMethod是通过动态添加IMP进缓存的,此时lookupImpOrNilTryCache返回的IMP是wgy_sayHello,解决了方法找不到而导致的奔溃
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//系统默认实现了resolveClassMethod,所以不会为空
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
return;
}
//返回类
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//inst是类,cls是元类
// 给nonmeta类发送消息,查看resolveClassMethod中是否实现了sel
//nonmeta类是是消息接收者,所以resolveClassMethod是类方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//类方法相当于元类中的实例方法,同样去快速和慢速的查找
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
//...
}
else {
//...
}
}
}
分析:根据上面的注释分析要点
- 系统同样在NSObject中
默认实现了resolveClassMethod,目的是容错为了系统稳定 resolveClassMethod是类方法- 类方法是在元类中的,
resolveClassMethod实现IMP其实是在元类中添加IMP
demo实现类的动态方法决议
新建LGPerson,调用不存在的say666()类方法,[LGPerson performSelector:@selector(say666)];
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"----进入类的动态方法决议----");
if(@selector(say666)==sel){
IMP selimp=class_getMethodImplementation(objc_getMetaClass(class_getName(self)), @selector(wgy_say666));
Method selmethod=class_getInstanceMethod(objc_getMetaClass(class_getName(self)), @selector(wgy_say666));
const char * type=method_getTypeEncoding(selmethod);
return class_addMethod(objc_getMetaClass(class_getName(self)), sel, selimp, type);
}
return [super resolveClassMethod:sel];
}
+(void)wgy_say666{
NSLog(@"--%s--",__func__);
}
输出:
----进入类的动态方法决议----
--+[LGPerson wgy_say666]--**
分析:
类方法在元类中,resolveClassMethod实现类方法的IMP其实在元类中添加IMP- wgy_say666必须是类方法,因为只有
类方法才在元类中 - 对于系统而言不存在类方法,只有对象方法,
类是元类的实例。
resolveClassMethod坑点
我们上面说了一个坑点:如果resolveClassMethod中没有IMP,就在resolveInstanceMethod再查找一次?我们验证一下,在LGPerson中resolveClassMethod不实现IMP,在resolveInstanceMethod中实现IMP。[LGPerson performSelector:@selector(sayHello)];
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"----进入类的动态方法决议----");
return [super resolveClassMethod:sel];
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"----进入对象的动态方法决议----");
if([NSStringFromSelector(sel) isEqualToString:@"sayHello"]){
IMP selimp=class_getMethodImplementation(self, @selector(wgy_sayHello));
Method selmethod=class_getInstanceMethod(self, @selector(wgy_sayHello));
const char * type=method_getTypeEncoding(selmethod);
return class_addMethod(self, sel, selimp, type);
}
return [super resolveInstanceMethod:sel];
}
-(void)wgy_sayHello{
NSLog(@"%s",__func__);
}
输出:
----进入类的动态方法决议----
分析:调试发现只执行了resolveClassMethod,而没有执行resolveInstanceMethod,同时程序崩溃了。我们上面分析resolveInstanceMethod(id inst, SEL sel, Class cls)时,inst是对象,cls是类,在类中添加IMP,现在进入resolveClassMethod,inst是类,cls是元类,应该是在元类中查找IMP,而我们通过resolveInstanceMethod动态添加的IMP是添加进类中的,所以在元类中找不到IMP。根据isa走位图以及类的继承,如果我们在根类中``实现了resolveInstanceMethod那么就可以彻底解决这个问题,因为所有类都继承NSObject,所有类的元类最终都指向根元类。
NSObject整合resolveInstanceMethod
添加一个NSObject分类, LGPerson* person=[[LGPerson alloc]init]; [person performSelector:@selector(sayHello)]; [LGPerson performSelector:@selector(say666)];
+(BOOL)resolveInstanceMethod:(SEL)sel{
if([NSStringFromSelector(sel) isEqualToString:@"sayHello"]){
IMP selimp=class_getMethodImplementation(self, @selector(wgy_sayHello));
Method selmethod=class_getInstanceMethod(self, @selector(wgy_sayHello));
const char * type=method_getTypeEncoding(selmethod);
return class_addMethod(self, sel, selimp, type);
}else if([NSStringFromSelector(sel) isEqualToString:@"say666"]){
IMP selimp=class_getMethodImplementation(objc_getMetaClass(class_getName(self)), @selector(wgy_say666));
Method selmethod=class_getInstanceMethod(objc_getMetaClass(class_getName(self)), @selector(wgy_say666));
const char * type=method_getTypeEncoding(selmethod);
return class_addMethod(objc_getMetaClass(class_getName(self)), sel, selimp, type);
}else{
return NO;
}
}
-(void)wgy_sayHello{
NSLog(@"%s",__func__);
}
+(void)wgy_say666{
NSLog(@"%s",__func__);
}
输出
-[NSObject(wgy) wgy_sayHello]
+[NSObject(wgy) wgy_say666]
分析:上面例子验证了我们的分析。在分类中做动态方法解析可以解耦,可以无侵入方式添加监控。
总结
- 在
resolveInstanceMethod动态决议方法中实现对象方法的IMP,可以解决objc_msgForward_impcache奔溃 - 在
resolveClassMethod动态决议方法中实现类方法的IMP,可以解决objc_msgForward_impcache奔溃,注意resolveClassMethod动态添加的IMP是在元类中的 NSObject根类中实现resolveInstanceMethod可以彻底解决objc_msgForward_impcache奔溃,NSObject是所有类的父类。