objc_msgSend流程分析之 动态方法决议 / 消息转发

在前面两篇文章iOS-底层原理 objc_msgSend流程分析之慢速查找objc_msgSend流程分析之缓存查找,分别分析了objc_msgSend的快速查找和慢速查找,在这两种都没找到方法实现的情况下,苹果给了两个建议

  • 动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
  • 消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发

方法未实现报错详解

报错如图

请添加图片描述

方法未实现报错源码

根据慢速查找的源码,我们发现,其报错最后都是走到**__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_forward_handler,有如下实现,本质是调用的objc_defaultForwardHandler方法
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
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;


看着objc_defaultForwardHandler有没有很眼熟,这就是我们在日常开发中最常见的错误:没有实现函数,运行程序,崩溃时报的错误提示。

防止方法未实现的崩溃。

三次方法查找的挽救机会

【第一次机会】动态方法决议->消息转发流程
【第二次机会】快速转发
【第三次机会】慢速转发

动态方法决议

在慢速查找流程未找到方法实现时,**首先会尝试一次动态方法决议,**其源码实现如下:

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 (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    //如果方法解析中将其实现指向其他方法,则继续走方法查找流程
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}


主要分为以下几步

判断类是否是元类

  • 如果是类,执行实例方法的动态方法决议resolveInstanceMethod
  • 如果是元类,执行类方法的动态方法决议resolveClassMethod,如果在元类中没有找到或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod中查找,主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议
  • 如果动态方法决议中,将其实现指向了其他方法,则继续查找指定的imp,即继续慢速查找lookUpImpOrForward流程

请添加图片描述

实例方法

针对实例方法调用,在快速-慢速查找均没有找到实例方法的实现时,我们有一次挽救的机会,即尝试一次动态方法决议,由于是实例方法,所以会走到resolveInstanceMethod方法,其源码如下

请添加图片描述

在resolveInstanceMethod调用后,又调用了一次lookUpImpOrNil;我们知道该方法如果找到对应imp之后会插入到对象类的缓存中去,方便后续使用;另一个是方便debug进行打印。

主要分为以下几个步骤:

  • 在发送resolveInstanceMethod消息前,需要查找cls类中是否有该方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
  • 如果没有,则直接返回 如果有,则发送
  • resolveInstanceMethod消息
  • 再次慢速查找实例方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找实例方法

实例方法的resolve
为self动态增加sayMaster的实现.使用runtime-api

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
    Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
    const char *type  = method_getTypeEncoding(sayMMethod);
    return class_addMethod(self, sel, imp, type);
}

当然也可以参考apple官方文档

类方法

针对类方法,与实例方法类似,同样可以通过重写resolveClassMethod类方法来解决前文的崩溃问题,

 + (BOOL)resolveClassMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(objc_getMetaClass("ZMPerson"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("ZMPerson"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}

resolveClassMethod类方法的重写需要注意一点,传入的cls不再是类,而是元类,可以通过objc_getMetaClass方法获取类的元类,原因是因为类方法在元类中是实例方法

实例方法与类方法的区别

唯一的区别就是类方法是需要添加到元类中的,所以需要先找到元类objc_getMetaClass.

根据观察resolveInstanceMetho会走2次

  • 第一次是在查询方法时lookupimp中调用的
  • 实例方法在动态方法决议时执行的是resolveInstanceMethod方法进行补救。
  • 类方法在动态方法决议时执行的是resolveClassMethod和resolveInstanceMethod方法进行补救。

resolveClassMethod源码实现

请添加图片描述

实现逻辑于resolveInstanceMethod基本相同

在NSObject中实现resolveInstanceMethod,也可以解决类方法找不到问题

  • 在源码中,如果是元类的话,会有下面这行代码,如果在没有找到类方法,那么就会在元类的对象方法解析方法中查找。

if (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
            resolveInstanceMethod(inst, sel, cls);
        }
  • · 在isa走位图中,继承关系是:元类 -> 根元类 -> NSObject ->
    nil。元类最终继承于NSObject,那么我们在NSObject中实现resolveInstanceMethod,那么类方法也会最终执行到NSObject中。

NSObject分类

@implementation NSObject (LG)

// 调用方法的时候 - 分类

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    

    NSLog(@"%@ 来了",NSStringFromSelector(sel));
    if (sel == @selector(say666)) {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
   
    return NO;
}
@end


【第二次机会】快速转发

针对前文的崩溃问题,如果动态方法决议也没有找到实现,则需要在ZMPerson中重写forwardingTargetForSelector方法,将ZMPerson的实例方法的接收者指定为ZMSon的对象(ZMSon类中有say666的具体实现),如下所示

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

//     runtime + aSelector + addMethod + imp
    //将消息的接收者指定为LGStudent,在LGStudent中查找say666的实现
    return [ZMSon alloc];
}

也可以直接不指定消息接收者,直接调用父类的该方法,如果还是没有找到,则直接报错

【第三次机会】慢速转发

针对第二次机会即快速转发中还是没有找到,则进入最后的一次挽救机会,即在ZMPerson中重写methodSignatureForSelector,如下所示

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
}


所以,由上述可知,无论在forwardInvocation方法中是否处理invocation事务,程序都不会崩溃。

总结

到目前为止,objc_msgSend发送消息的流程就分析完成了,在这里简单总结下

【快速查找流程】首先,在类的缓存cache中查找指定方法的实现

【慢速查找流程】如果缓存中没有找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找

【动态方法决议】如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法

【消息转发】如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发

如果转发之后也没有,则程序直接报错崩溃unrecognized selector sent to instance

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值