消息传递和消息转发机制

136 阅读5分钟

#iOS笔记/复习笔记

消息传递机制

1、进入_objc_msgSend后首先判断消息的接受者是否为nil或者是否使用了tagPointer技术。 2.消息查找:本类缓存 -> 本类方法列表(找到并添加到缓存) -> 遍历父类缓存 -> 父类方法列表(找到后添加到本类的缓存中) -> 一直找到基类; 3.还没有找到触发动态方法解析流程; 3.还有没有则触发消息转发流程;

消息转发机制原理

先看一下结构图

1197929-2de08be6100cd895.jpg

从全局来看,消息转发机制分为三大步骤:

  1. Method resolution 动态方法解析处理阶段
  2. Fast forwarding 快速转发阶段
  3. Normal forwarding 常规转发阶段 如果不想抛出unrecognized selector的报错,就需要从这三步进行补救;

Method resolution 方法解析处理阶段

如果调用了对象方法首先会进行+(BOOL)resolveInstanceMethod:(SEL)sel判断 如果调用了类方法 首先会进行 +(BOOL)resolveClassMethod:(SEL)sel判断 两个方法都为类方法,如果YES则能接受消息 NO不能接受消息 进入第二步 我们先调用一下对象方法

 [self.test performSelector:@selector(testFunction)];

然后在resolveInstanceMethod进行补救,这里用到了我封装的一个Runtime工具类,这里暂时不做展开讲解,会在后续其他文章里面展开讲解,功能是对一个类添加一个方法。

+(BOOL)resolveInstanceMethod:(SEL)sel{
    //判断是否为外部调用的方法
    if ([NSStringFromSelector(sel) isEqualToString:@“testFunction”]) {
        /**
         对类进行对象方法 需要把方法添加进入类内
         */
        [LMRuntimeTool addMethodWithClass:[self class] withMethodSel:sel withImpMethodSel:@selector(addDynamicMethod)];
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

下面我们再来调用一下TestMessage的类方法

[[TestMessage class] performSelector:@selector(testClassFunction)];

如果调用类方法需要在resolveClassMethod 进行补救判断

+(BOOL)resolveClassMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@“testClassFunction”]) {
        /**
         对类进行添加类方法 需要讲方法添加进入元类内
         */
        [LMRuntimeTool addMethodWithClass:[LMRuntimeTool getMetaClassWithChildClass:[self class]] withMethodSel:sel withImpMethodSel:@selector(addClassDynamicMethod)];
        return YES;
    }
    return [super resolveClassMethod:sel];
}

这里有一个需要特别注意的地方,类方法需要添加到元类里面,OC中所有的类本质上来说都是对象,对象的isa指向本类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自己,这样的话就形成了一个闭环。 [LMRuntimeTool getMetaClassWithChildClass:[self class]]这个方法是用来获取本类的元类,对元类添加需要添加的方法。 经过上面两种类型的补救,果然对象方法和类方法都不在抛出异常了,并且打印了数据

2018-08-06 15:25:25.667572+0800 MessageForwardDemo[3599:949889] 动态添加类方法
2018-08-06 15:25:25.667612+0800 MessageForwardDemo[3599:949889] 动态添加方法

第二步:Fast forwarding 快速转发阶段 (后面阶段都针对对象来处理,不考虑类方法)

如果在上一步的2个方法内返回的为YES则能接受消息 NO不能接受消息 进入第二步,我们先把上面方法内的处理方案注释掉,让消息转发进入第二步。 我们新创建一个BackupTestMessage类,里面声明和实现testFunction方法,用来当作备用响应者。

-(id)forwardingTargetForSelector:(SEL)aSelector{
  if ([NSStringFromSelector(aSelector) isEqualToString:@“testFunction”]) {
        return [BackupTestMessage new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

因为一个对象内部可能还有其他可能响应的对象,所以这个方法是转发SEL去对象内部的其他可以响应该方法的对象。 这里创建的一个BackupTestMessage的实例内定义的有testFunction方法,所以返回这个实例之后,果然不再报错了,并且根据打印也能看得出来走了BackupTestMessage这个类的实例方法

2018-08-06 15:27:43.234733+0800 MessageForwardDemo[3629:951288] 备用类的对象方法testFunction

已经让备用的对象去响应了TestMessage本身无法响应的一个SEL

第三步:Normal forwarding 常规转发阶段

如果第2步返回self或者nil,则说明没有可以响应的目标 则进入第三步。 第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象,但是第三步切换响应目标更复杂一些,第二步里面只需返回一个可以响应的对象就可以了,第三步还需要手动将响应方法切换给备用响应对象。 第三步有2个步骤:

(1)-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
(2)-(void)forwardInvocation:(NSInvocation *)anInvocation

在第(1)步中,返回SEL方法的签名,返回的签名是根据方法的参数来封装的。

  • 手动创建签名 但是尽量少使用 因为容易创建错误 可以按照这个规则来创建 blog.csdn.net/ssirreplace… 根据OBJC的编码类别进行编写后面的char (但是容易写错误,所以建议使用下面的方法)
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
//写法例子
//例子”v@:@“
//v@:@ v:返回值类型void;@ id类型,执行sel的对象;:SEL;@参数
//例子”@@:”
//@:返回值类型id;@id类型,执行sel的对象;:SEL
  • 自动创建签名
BackupTestMessage * backUp = [BackupTestMessage new];
NSMethodSignature * sign = [backUp methodSignatureForSelector:aSelector];

使用对象本身的methodSignatureForSelector自动获取该SEL对应类别的签名

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //如果返回为nil则进行手动创建签名
   if ([super methodSignatureForSelector:aSelector]==nil) {
        NSMethodSignature * sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sign;
    }
		//自动签名
    return [super methodSignatureForSelector:aSelector];
}

上方的第(1)步中如果调用返回有签名 则进入消息转发最后一步

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    //创建备用对象
    BackupTestMessage * backUp = [BackupTestMessage new];
    SEL sel = anInvocation.selector;
    //判断备用对象是否可以响应传递进来等待响应的SEL
    if ([backUp respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:backUp];
    }else{
       // 如果备用对象不能响应 则抛出异常
        [self doesNotRecognizeSelector:sel];
    }
}

在三个步骤的每一步,消息接受者都还有机会去处理消息。同时,越往后面处理代价越高,最好的情况是在第一步就处理消息,这样runtime会在处理完后缓存结果,下回再发送同样消息的时候,可以提高处理效率。第二步转移消息的接受者也比进入转发流程的代价要小,如果到最后一步forwardInvocation的话,就需要处理完整的NSInvocation对象了。

参考链接: iOS Runtime 消息转发机制原理和实际用途 - 简书