底层原理-10-objc_msgSend之方法动态决议

1.动态方法决议分析

之前我们在消息的慢速查找中,对于没有找到的方法,苹果会给我们1次机会进行弥补就是之前慢速查找的resolveMethod_locked(inst, sel, cls, behavior)方法动态决议。
我们日常开发如果方法没有实现的话会报错,方法没有找到

1.1崩溃日志打印分析

截屏2021-07-01 下午3.02.48.png
是因为走到方法转发中_objc_msgForward_impcache进入源码可知:

STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
复制代码

主要调用了_objc_forward_handler方法,全局搜素得到

objc_defaultForwardHandler(id self, SEL sel)
{
    _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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
复制代码
if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
复制代码

就是平常我们方法找不到调用的方法,添加了当前的类和方法等信息。会在lldb打印出崩溃的信息。那么我们是否可以拦截防止崩溃呢。先看下动态方法决议流程

1.2 resolveMethod_locked分析

    
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());//

    runtimeLock.unlock();
//当前类的isa 不是元类的话
    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);//
}
复制代码
  • 因为在oc中方法存在实例方法类方法,所以先判断当前类的isa是不是元类,不是的话走resolveInstanceMethod是的话先走resolveClassMethod
  • 在元类判断中,调用 resolveClassMethod没有解决,再次调用resolveInstanceMethod
  • 最后在去类的方法列表里查找下我们替换的方法,走慢速查找流程。

我们在KBStudent中重写一下这个resolveInstanceMethod方法

截屏2021-07-02 上午10.43.40.png
确实进来了调用了KBStudent的resolveInstanceMethod方法,但是为什么走了2次。

1.2.1 resolveInstanceMethod分析

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);//定义解决方法

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.去查找一下有没有解决问题这个方法,没有的的话,就返回,但是这个方法基本上不会进来的,系统在NSObject中有默认实现这个方法
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //对着这个方法发送消息看下有没有解决问题
   /* + (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}*/
    bool resolved = msg(cls, resolve_sel, sel);// return Yes就是说你处理了,系统默认是NO,你没处理 

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);//再次慢速查找这个解决方法的imp,重新指向的方法。

    if (resolved  &&  PrintResolving) {
        if (imp) {//操作的信息写入
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
                         //有imp
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));//没有imp
        }
    }
}
复制代码
  • 在调用实例方法的时候没有实现,进入实例方法的动态决议。我们是否实现了resolveInstanceMethod,但是通常NSObject会实现这个方法返回NO,没有处理。
  • resolveInstanceMethod发送消息,再次慢速查找这个解决方法的imp但是不会进行消息转发,重新指向的方法。imp 查找过程lookUpImpOrNilTryCache-> _lookUpImpTryCache->lookUpImpOrForward
  • 添加的解决方法的话 lookUpImpOrForwardTryCache->_lookUpImpTryCache再次慢速查找Imp找到进行消息发送。

1.2.2打印2次的原因

正常实例对象的话没有找到方法的话就会在慢速查找中走resolveInstanceMethod方法,但是走了2次说明,还有别的地方再次调用了resolveInstanceMethod方法。我们在消息转发的地方打上一个断点,进行调试

截屏2021-07-02 下午3.33.11.png
全局搜搜class_getInstanceMethod得到

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.查找方法列表,尝试去找一下方法解析
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
复制代码

调试源码的时候有的时候会有很多系统方法,导致我们过程很烦,容易错过。可以直接在自己新建的工程打断点打印内存信息

截屏2021-07-02 下午4.33.31.png
这是一个NSObejct的实例方法,我们在使用实例对象调用方法的时候出错,自身第一次没解决的话CoreFoundation应该再次调用了class_getInstanceMethod方法,里面的 lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER)的调用是我们再次调用了上面的流程;

1.2.3 解决方案

我们可以把未实现的方法给他重新定义,给他一个新的Imp,解决崩溃

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%@来了",NSStringFromSelector(sel));
    if (sel == @selector(sayNB)) {
        
        IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));
        
        Method method = class_getInstanceMethod(self, @selector(sayNotNB));
        
        const char *type = method_getTypeEncoding(method);
        
        return  class_addMethod(self, sel , imp, type);
    }

    return [super resolveInstanceMethod:sel];
    
}
-(void)sayNotNB
{
    
    NSLog(@"%s",__func__);
}
复制代码

截屏2021-07-02 下午4.36.31.png

1.2.3 类方法未实现解决

同样的如果是类方法未实现,崩溃解决

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        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);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

复制代码

原理一样的,至是去类中找resolveClassMethod类方法

  • 在调用类方法的时候没有实现,进入类方法的动态决议。我们是否实现了resolveClassMethod,但是通常NSObject会实现这个方法返回NO,没有处理。
  • resolveClassMethod发送消息,再次慢速查找这个解决方法的imp但是不会进行消息转发,重新指向的方法,也就是重定向。imp 查找过程lookUpImpOrNilTryCache-> _lookUpImpTryCache->lookUpImpOrForward
  • 添加的解决方法的话 lookUpImpOrForwardTryCache->_lookUpImpTryCache再次慢速查找Imp找到进行消息发送。
  • 没有解决的话或者为空,会去元类的对象中查找调用实例方法决议

截屏2021-07-02 下午4.51.18.png
走了2次resolveClassMethod方法,说明和上面实例方法一样调用了class_getInstanceMethod打印一下确实这样

截屏2021-07-02 下午4.58.13.png

+(BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"%@来了",NSStringFromSelector(sel));
    if (sel == @selector(sayHello)) {
        Class metaClass = object_getClass(self);
        IMP imp = class_getMethodImplementation(metaClass, @selector(sayLoveYou));
        Method method = class_getClassMethod(metaClass, @selector(sayLoveYou));
        const char* type = method_getTypeEncoding(method);
        return  class_addMethod(metaClass, sel, imp, type);
    }
    
    return  [super resolveClassMethod:sel];
}
+(void)sayLoveYou{
    
    NSLog(@"%s",__func__);
    
}
复制代码

添加类方法的话要把类改成元类,类方法在元类方法列表中。

2.统一的处理AOP思想

因为我们开发中使用的对象懂继承于NSObject,那么时不是可以在NSObject的解决这个问题,方法查找的过程是沿着父类链进行查找。我们创建NSObjectCategory

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%@来了",NSStringFromSelector(sel));
    if (sel == @selector(sayHello)) {

        IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));

        Method method = class_getInstanceMethod(self, @selector(sayNotNB));

        const char *type = method_getTypeEncoding(method);

        return  class_addMethod(self, sel , imp, type);
    }else if(sel == @selector(sayNB))
    {
        
        IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));

        Method method = class_getInstanceMethod(self, @selector(sayNotNB));

        const char *type = method_getTypeEncoding(method);

        return  class_addMethod(self, sel , imp, type);
        
    }

    return NO;

}
-(void)sayNotNB
{

    NSLog(@"%s",__func__);
}
复制代码

截屏2021-07-02 下午5.48.54.png
类方法还是实例方法都避免了崩溃。为什么不用resolveClassMethod方法?因为

 resolveClassMethod(inst, sel, cls);//进入类方法决议
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {//如果没有在类方法中找到解决的方法决议
            resolveInstanceMethod(inst, sel, cls);//再次去调用实例方法决议
        }
复制代码

所以NSObject 不管是类方法还是实例方法最终都会走resolveInstanceMethod。根元类的父类是NSObject,在oc中NSObject没有父类,他就是所有对象的父类
这就是AOP思想,面向切面编程。无侵入,不耦合。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

3.总结

  • 在慢速查找中,如果没有找到,苹果会给你一次机会,去重写resolveInstanceMethodresolveClassMethod方法。
  • class_addMethod注意当前是类方法还是实例方法。
  • resolveMethod_locked走2次是因为一次是正常的查找当前类的方法没查到进入,第二次是CoreFoundation 调用class_getInstanceMethod进行动态决议流程
  • 在oc中对象都继承于NSObject,所以我们可以在Category统一处理resolveInstanceMethod
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享