objc_msgSend之动态解析和消息转发

前一个博客介绍了objc_msgSend消息发送的机制,本节我们从动态解析以及之后的消息转发来继续了解。

一、动态解析

我们通过上面的forward_imp来深入了解一下,我们通过对forward_imp一层层的调用关系最后定位到了最后这张图片,即当找不到方法实现的时候,就尝试一次方法解析。一次动态解析可以将方法加入到cache中。

我们继续向下看resolveMethod_locked的实现:

这里对cls是否是元类进行了判断,如果是元类,说明是类方法的调用,则调用resolveClassMethod,如果是类,则说明是实例方法的调用,则调用resolveInstanceMethod。

 

我们可以看到在resolveClassMethod、resolveInstanceMethod通过objc_msgSend去调用resolveClassMethod: 和resolveInstanceMethod:而resolveClassMethod其中的cls本来就是元类,通过getMaybeUnrealizedNonMetaClass来获取元类对应的类对象,然后再通过类对象发送消息(相当于类方法)。

那么此时的resolveInstanceMethod: 和 resolveClassMethod: 的实现就交给了开发者,可以利用这两个函数来防止程序找不到方法时所导致的奔溃。// 第一根稻草,使用动态解析,动态添加方法

@implementation LGPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s--%@",__func__,NSStringFromSelector(sel));
    //动态实现添加test方法
//该方法中将test的实现动态替换为other,所以我们查找到other方法,调用方法,结束查找并将方法缓存到cache中。
if(sel == @selector(test)){ IMP imp = class_getMethodImplementation(self.class, @selector(method1)); } return YES; } -(void) method1{ NSLog(@"%s",__func__); }

 

这里借助这篇博客(https://juejin.cn/post/7095673959537967135/)里的一张图来理解一下动态解析的流程:

二、消息转发流程

如果动态解析阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段,也有叫快速转发,和完全消息转发阶段,也叫慢速转发阶段。

2.1 消息快速转发

实现forwardingTargetForSelector方法,并返回替换的对象。实现如下:消息转发会去LGTeacher里面去找method1

//第二根稻草,使用快速消息转发,找其他对象来实现方法

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(method1)) {
        return [LGTeacher new]
    }
    return nil;
}
@implementation LGTeacher
//从消息转发而来
- (void)method1 {
    NSLog(@"%s__ %@",__FUNCTION__,[self class]);
}
@end

2.2 消息慢速转发

如果forwardingTargetForSelector方法返回的是nil,那么我们还有最后一根稻草可以抓住-完全消息转发。相比于快速抓发,不经可以替换消息接受者,还能替换方法。

//消息的慢速转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    //方法的有效签名
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
-(void) forwardInvocation:(NSInvocation *)anInvocation{
    LGTeacher *t = [LGTeacher new];
    if([self respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:self];
    }else if([t respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:t];
    }else{
        NSLog(@"该功能正在开发中");
    }
}

三、总结

3.1 动态解析和消息转发流程总结

  • 在消息发送的时候,如果在cache方法列表中都没有找到方法实现,会来到动态解析阶段
  • 通过调用resolveInstanseMethodresolveClassMethod,来动态的解析方法的实现。
  • 如果在方法里返回YES,则会进入消息转发阶段,通过frowardingTargetSelector,可以将消息转发到能处理消息的实例里面。
  • 如果没有进行快速转发,可以methodSignatureForSeletor 和 forwardInvocation 来进行完全消息转发。
  • 注意:resolveInstanseMethod会被调用两次,第一次是快速转发的时候调用,第二次是慢速转发的时候调用methodSignatureForSeletor时,其内部调用了class_getInstanceMethod方法,在这个方法的内部又调用了lookUpImpOrForward,此时消息的动态决议就会被调用两次。

3.1.1 类的实例方法动态方法解析:

1.缓存查找,没有找到
2.类查找/父类查找
3.动态方法解析 resolveMethod_locked,再去找cls的元类的resolveInstacenMethodimp
4.如果还是没有实现,再去找根元类,直到找到 NSObject根元类 的 resolveInstacenMethodimp
5.系统主动给类对象发送一条resolveInstacenMethod消息(找到cls的元类继承链中的resolveInstacenMethod并调用它,并缓存到本类的cache)(这是为了补救imp没有找到的错误)
6.接着还会往类对象cache通过sel查找一次resolveInstacenMethodimp
 
3.1.2 类方法动态方法解析:
1.缓存查找,没有找到
2.元类/根元类的方法列表,没有找到
3.动态方法解析resolveMethod_locked,再去找cls的元类的resolveClassMethod的imp
4.如果还是没有实现,就去找元类->根元类直到找到NSObjec根元类resolveInstacenMethodimp
5.给元类发送一条消息(这里苹果做了处理,下面做源码分析),让sel+imp绑定

3.2 苹果设计这两个阶段的原理

首先,确实动态解析,快速转发和慢速转发都能做到防止程序unrecognized selector sent to instance 类型的crash,这三种方式也被称做消息发送的三根救命稻草。。这些阶段的前提都是在没有找到方法实现,设计这些并不会影响消息发送的效率。而每根稻草实际的实现原理是不同的,动态解析是动态的去添加方法实现,消息转发是让一个替代者来实现。
posted on 2022-05-11 17:18  suanningmeng98  阅读(105)  评论(0编辑  收藏  举报