四、消息转发流程
res方法如果处理的话就涉及到动态添加方法的内容,第七小节讲解。
第一步:Method resolution 方法解析处理阶段
对象在收到无法解读的消息后,首先会调用+(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel, 询问是否有动态添加方法来进行处理;如果YES则能接受消息, NO不能接受消息 进入第二步;说明如下
+ (BOOL)resolveInstanceMethod:(SEL)sel {
//判断是否为外部调用的方法
if ([NSStringFromSelector(sel) isEqualToString:@"speak"]) {
/**
对类进行对象方法 需要把方法添加进入类内
*/
[MHYRuntimeTool addMethodWithClass:[self class] withMethodSel:sel withImpMethodSel:@selector(addDynamicMethod)];
return YES;
}
return [super resolveInstanceMethod:sel];
}
当People 收到了未知 speak选择子的消息的时候,如果是实例方法会首选调用上文的resolveInstanceMethod:方法,方法内通过判断选择子然后通过class_addMethod方法动态添加了一个speak的实现方法来解决掉这条未知的消息,此时消息转发过程提前结束。
如果当People 收到speak 这条未知消息的时候,第一步返回的是No,也就是没有动态新增实现方法的时候就会调用第二步。
如果调用类方法需要在resolveClassMethod 进行补救判断
+ (BOOL)resolveClassMethod:(SEL)sel {
//判断是否为外部调用的方法
if([NSStringFromSelector(sel) isEqualToString:@"speakClass"]){
/**
对类进行添加类方法 需要将方法添加进入元类内
*/
[MHYRuntimeTool addMethodWithClass:[MHYRuntimeTool getMetaClassWithTargetClass:[self class]] withMethodSel:sel withImpMethodSel:@selector(addClassDynamicMethod)];
return YES;
}
return [super resolveClassMethod:sel];
}
这里有一个需要特别注意的地方,类方法需要添加到元类里面,OC中所有的类本质上来说都是对象,对象的isa指向本类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自己,这样的话就形成了一个闭环。
第二步:Fast forwarding 快速转发阶段 (后面阶段都针对对象来处理,不考虑类方法)
既然第一步已经问过了,没有新增方法,那就问问有没有别人能够帮忙处理一下,调用的是- (id)forwardingTargetForSelector:(SEL)aSelector这个方法。我们先把上面方法内的处理方案注释掉,让消息转发进入第二步。我们新创建一个keepBackPeople类,里面声明和实现speak方法,用来当作备用响应者。
-(id)forwardingTargetForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"speak"]) {
return [keepBackPeople new];
}
return [super forwardingTargetForSelector:aSelector];
}
因为一个对象内部可能还有其他可能响应的对象,所以这个方法是转发SEL去对象内部的其他可以响应该方法的对象。
第三步:Normal forwarding 常规转发阶段
如果第二步返回self或者nil,则说明没有可以响应的目标 则进入第三步。
第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象,但是第三步切换响应目标更复杂一些,第二步里面只需返回一个可以响应的对象就可以了,第三步还需要手动将响应方法切换给备用响应对象。
第三步有2个步骤,首先:
(1)- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{}
此步中,返回SEL方法的签名,返回的签名是根据方法的参数来封装的。手动创建签名 但是尽量少使用 因为容易创建错误 可以按照这个规则来创建https://blog.youkuaiyun.com/ssirreplaceable/article/details/53376915
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
/*手动创建签名 但是尽量少使用 因为容易创建错误 可以按照这个规则来创建
https://blog.youkuaiyun.com/ssirreplaceable/article/details/53376915
//写法例子
//例子"v@:@"
//v@:@ v 返回值类型void;@ id类型,执行sel的对象;: SEL;@ 参数
//例子"@@:"
//@ 返回值类型id;@ id类型,执行sel的对象;: SEL
*/
//如果返回为nil则进行手动创建签名
if ([super methodSignatureForSelector:aSelector]==nil) {
NSMethodSignature * sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
其次:
(2)-(void)forwardInvocation:(NSInvocation *)anInvocation
上方的第(1)步中如果调用返回有签名 则进入消息转发最后一步
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// 创建备用对象
keepBackPeople *keepBack = [keepBackPeople new];
SEL sel = anInvocation.selector;
//判断备用对象是否可以响应传递进来等待响应的SEL
if ([keepBackPeople respondsToSelector:sel]) {
[anInvocation invokeWithTarget:keepBack];
}else {
//如果备用对象不能响应,则抛出异常
[self doesNotRecognizeSelector:sel];
}
}
在三个步骤的每一步,消息接受者都还有机会去处理消息。同时,越往后面处理代价越高,最好的情况是在第一步就处理消息,这样runtime会在处理完后缓存结果,下回再发送同样消息的时候,可以提高处理效率。第二步转移消息的接受者也比进入转发流程的代价要小,如果到最后一步forwardInvocation的话,就需要处理完整的NSInvocation对象了。
实际用途:
(1)为 @dynamic 实现方法
使用 @synthesize 可以为 @property 自动生成 getter 和 setter 方法(现 Xcode 版本中,会自动生成),而 @dynamic 则是告诉编译器,不用生成 getter 和 setter 方法。当使用 @dynamic 时,我们可以使用消息转发机制,来动态添加 getter 和 setter 方法。当然你也用其他的方法来实现。https://www.jianshu.com/p/6b05ba0e81e0
(2)实现多重代理
利用消息转发机制可以无代码侵入的实现多重代理,让不同对象可以同时代理同个回调,然后在各自负责的区域进行相应的处理,降低了代码的耦合程度。https://blog.youkuaiyun.com/kingjxust/article/details/49559091
(3)间接实现多继承
Objective-C本身不支持多继承,这是因为消息机制名称查找发生在运行时而非编译时,很难解决多个基类可能导致的二义性问题,但是可以通过消息转发机制在内部创建多个功能的对象,把不能实现的功能给转发到其他对象上去,这样就做出来一种多继承的假象。转发和继承相似,可用于为OC编程添加一些多继承的效果,一个对象把消息转发出去,就好像他把另一个对象中放法接过来或者“继承”一样。消息转发弥补了objc不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂https://www.jianshu.com/p/9601e84177a3
demo传送门
参考链接:https://www.jianshu.com/p/9263720cbd91
五、Method-Swizzling
1、什么是Method-Swizzling
交换两个方法的方法实现。
2、涉及的三个方法如下图