runtime总结二之消息机制(包括消息转发,消息交换的黑魔法)

Posted li15809284891

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了runtime总结二之消息机制(包括消息转发,消息交换的黑魔法)相关的知识,希望对你有一定的参考价值。

runtime的消息机制

前面提到过编译器最终会把我们的消息发送转化为函数调用

  • 消息发送 [object sendMassage]
    首先编译器会在运行时将上面的例子转化为objc_msgSend(obj,@selector(sendMassage))这个函数,转换的时候除了方法本身的参数之外,还有两个隐藏的参数一个是id类型的,代表对象的类型,还是一个是SEL类型的,是函数对应的方法的编号,接下来就会按照下面的流程来调用这个方法
  1. 通过obj的isa指针找到其所对应的类。
  2. 通过SEL先去类的cache列表中找这个方法,如果就去找方法的实现,不存在,进入第3步
  3. 去类的method列表中找,如果就去找方法的实现,没有找到,根据类中的superclass指针去父类中找,一直到NSObject.
找到了方法之后就要去找方法的实现,那么如何找方法的实现呢,runtime提供了两种方式
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
在id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

可以看到这个方法中的第二行代码imp,可以通过这个imp来查找这个方法的实现,要是没有找到,runtime给我们提供了三次机会让我们的程序不会崩溃,也就是下面要提到的动态方法解析和消息转发(消息重定向,消息转发)
* 动态方法解析
resolveInstanceMethod或者resolveClassMethod给你提供一次添加方法实现的机会
下面例子,在student类中有一个没有实现的write方法,但是没有实现,如果我们掉用它,会因为找不到实现程序崩溃,有下面的挽救措施

//在这个c语言函数添加方法的实现
void test(){
    NSLog(@"我是动态添加的方法");
}
//然后实现这个方法,当这个方法返回no时或者没有实现时会进入消息转发
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(write)) {
        class_addMethod([self class], sel, (IMP)test, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}

如你添加了函数实现并且返回了yes,那么就会调用。不会进入下面的消息转发,否则进入下面的消息转发

  • 消息转发
消息转发分为两步,一次是消息的重定向,返回一个实现了该方法的对象,还有一次是真正的转发,是把这个函数的参数及相关信息打包成一个对象,把他发送给另外一个类,让他去处理(PS:消息重定向是返回一个提供了实现的对象,消息转发是将方法参数打包到一个对象里面,然后把这个对象发送出去)。
  1. 消息重定向:需要实现这个函数- (id)forwardingTargetForSelector:(SEL)aSelector,通过这个函数返回一个实现了该方法的对象。如果返回了self或者no的时候,就会进入消息转发,否则不会

定义一个新类,里面添加一个和student中未实现的方法同名的方法,而且这个方法有实现,假设叫testStudent,然后在student中实现下面的方法

//该方法返回一个添加了方法实现的对象,
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(write)) {
    //studentTest类中这个方法有实现
        StudentTest *test = [[StudentTest alloc]init];
        return test;
    }
    return [super forwardingTargetForSelector:aSelector];
}
  1. 消息转发:需要实现两个方法
    methodSignatureForSelector:返回一个NSInvocation*的对象,将方法的参数返回值封装在里面。
    - (void)forwardInvocation:(NSInvocation *)anInvocation,将该对象发送给一个提供了方法实现的对象。
    和上面的消息重定向一样,只不过student中要实现的方法有了差别
//这个方法返回一个NSInvocation*的对象,里面打包了有关于这个未实现方法的信息
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString*sel = NSStringFromSelector(aSelector);
    if ([sel isEqualToString:@"write"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL selector = [anInvocation selector];
    StudentTest *test = [[StudentTest alloc]init];
    if ([test respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:test];
    }
}

总结一下

发送一个消息:底部会转成objc_msgSend函数,这个函数除了方法的参数之外还有两个隐藏的参数self和_cmd,接下来就会按照下面的流程去调用这个函数

  1. 根据isa指针找到对象所属的类或者类所属的元类
  2. 先去类或者元类的cache列表中根据SEL去找这个方法。
  3. 没有找到,去method方法列表中找
  4. 还是没有找到,就去父类中找
  5. 找到了,根据SEL找到对应的IMP,调用这个函数
  6. 没有找到,进入动态方法解析或者消息转发。

动态方法解析:

runtime提供的第一次实现这个方法的机会,要实现resolveInstanceMethod/resolveClassMethod方法,给未实现的方法在运行时添加实现,返回no/不实现,进入消息转发。

消息转发一:消息重定向

消息重定向:返回一个实现了该方法的对象,要实现-(id)forwardingTargetForSelector:(SEL)aSelector函数,如果返会nil/self,则进入下面的消息转发二

消息转发二:消息转发

将未实现方法的相关信息打包成一个NSInvocation对象,然后交给一个类去实现。需要实现-(NSMethodSignature )methodSignatureForSelector:(SEL)aSelector和-(void)forwardInvocation:(NSInvocation )anInvocation方法

疑问?为什不直接找IMP而要通过SEL这个中间人呢?

SEL只是方法的编号,真是的实现是通过IMP来查找的,SEL和IMP之间是一一映射的关系,通过SEL我们可以改变他的IMP,然后让一个方法在不同的情况下有不同的实现,例如实现方法的交换,有时候我们需要给系统的方法添加一些自己的东西
1:可以通过一个子类继承于系统类,然后重写那个类的方法
2:通过分类,但是会覆盖系统的方法
3:写一个自己的方法,通过runtime在load方法中交换系统方法和自己的方法的实现
下面主要针对第三种例子举例:
第一步首先为我们要动手脚的系统方法类添加一个分类,
假设我们要为imageNamed添加一个判断nil的功能,先要为他添加一个分类,然后给系统的imageNamed方法添加前缀,明明一个自己的方法,如下
分类的h文件
这里写图片描述
分类的m文件,对于这里乍一看可能像递归,其实在第一次调用的系统的imageNamed方法时调用的是my_imageNamed方法,当第调用的my_imageNamed方法时其实在调用系统的imageNamed方法

交换
这里写图片描述
至于为什么要在load方法中写,我会在别的博客中提到。

以上是关于runtime总结二之消息机制(包括消息转发,消息交换的黑魔法)的主要内容,如果未能解决你的问题,请参考以下文章

RunTime(消息机制) + RunTime(消息转发)

iOS-Runtime消息发送转发机制

揭秘 Objective-C Runtime - 消息转发

runtime之消息转发

iOS面试粮食Runtime—消息传递和转发机制Method Swizzling

iOS面试粮食Runtime—消息传递和转发机制Method Swizzling