ios 底层原理之动态方法决议

209 阅读6分钟

前言

在上一篇底层原理之方法慢速查找流程我们探索了消息发送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断点调试一下

截屏2021-08-11 下午5.05.28.png 分析:

  • 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,现在进入resolveClassMethodinst是类,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是所有类的父类