OC消息发送分两步:
1.编译阶段
方法调用转换成objc_msgSend函数调用
不带参数:objc_msgSend(receiver,selector)
带参数:objc_msgSend(recevier,selector,org1,org2,…)
2.运行时阶段,
先从类到父类继承链中查找方法,没找到就消息转发
整个调用流程图如下
2.1.继承链中查找方法
实例对象调用一个方法,会首先在本类方法列表查找,如果没有,会在父类再查找,直到根类NSObject,在任何一层找到方法,则执行,如果到了最后根类NSObject还没有找到,才会触发Objective-C Runtime的消息转发机制。
类对象调用一个方法,会首先在本类的元类方法列表中查找,如果没有,会在元类的父类再查找,直到根类NSObject的元类,在任何一层找到方法,则执行,如果到了最后根类NSObject元类还没有找到,才会触发Objective-C Runtime的消息转发机制。
2.2.OC Runtime的消息转发机制
2.2.1.NSObject的消息转发机制包括动态方法解析、消息接受者重定向、消息重定向
消息转发流程如下:
1.先调用实例方法resolveInstanceMethod
如果作者在这里使用runtime动态添加对应的方法,并且返回yes。就万事大吉。对象找到了处理的方法,
并且将这个新增的方法添加到类的方法缓存列表
2.如果上面的方法返回NO的话,对象会调用forwardingTargetForSelector方法
允许作者选择其他的对象,处理这个消息。
这个方法,也是待会我们要做文章的地方。画重点。
3.如果上面两个方法都没有做处理,那么对象会执行最后一个方法methodSignatureForSelector,提供一个有效的方法签名,若提供了有效的方法签名,程序将会通过forwardInvocation方法执行签名。若没有提供方法签名就会触发doesNotRecognizeSelector方法,触发崩溃。
id proxy = [MessageSender proxyForObject1:[Dog new] object2:[Cat new]];
[proxy speak];
[proxy eat];
#import "MessageSender.h"
#import <objc/runtime.h>
@implementation Dog
- (void)speak {
NSLog(@"Dog speak");
}
@end
@implementation Cat
- (void)eat {
NSLog(@"Cat eat");
}
@end
@interface MessageSender()
@property (nonatomic, weak) id target1;
@property (nonatomic, weak) id target2;
@end
@implementation MessageSender
- (instancetype)initWithTarget1:(id)obj1 target2:(id)obj2 {
_target1 = obj1;
_target2 = obj2;
return self;
}
+ (id)proxyForObject1:(id)obj1 object2:(id)obj2 {
return [[MessageSender alloc] initWithTarget1:obj1 target2:obj2];
}
//动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 这里可以新增一个处理unknown selector的方法,
if (sel == @selector(speak)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *str) {
NSLog(@"block, val=%@", str);
}), "v@");
// class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:@");
}
return [super resolveInstanceMethod:sel];
}
+(BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
//消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
// 若返回一个其它的执行对象,那消息从id objc_msgSend ( id self, SEL op, ...)重新开始
if ([_target1 respondsToSelector:aSelector]) {
return _target1;
}
if ([_target2 respondsToSelector:aSelector]) {
return _target2;
}
return nil;
}
//消息重定向
//重写NSProxy如下两个方法,在处理消息转发时,将消息转发给真正的Target处理
/*
返回nil,Runtime 则会发出 -doesNotRecognizeSelector: 消息,程序 crash
返回了NSMethodSignature,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *sig;
sig = [_target1 methodSignatureForSelector:selector];
if (sig) {
return sig;
}
sig = [_target2 methodSignatureForSelector:selector];
return sig;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
id target;
if ([_target1 methodSignatureForSelector:invocation.selector]) {
target = _target1;
}else{
target = _target2;
}
[invocation invokeWithTarget:target];
}
@end
2.2.2NSProxy作为抽象类不能直接使用,它不提供初始化的方法,并且在接收到它没有响应的任何消息时引发异常,
因此只能继承并在具体的子类提供初始化或创建方法,并实现forwardInvocation:和methodSignatureForSelector:
NSProxy消息转发过程简单很多,接收到unknown selector后,直接回调
- (NSMethodSignature *)methodSignatureForSelector:和
- (void)forwardInvocation: