OC中的方法调用, 其实都是转换为 objc_msgSend() 函数的调用;
消息机制: 给方法调用者发送消息
- (void)resolvePlay:(NSString *)ball{
NSLog(@"%@",NSStringFromSelector(_cmd));
}
//方法(实现了直接调用,没有实现走->方案1动态方法解析)
-(void)play{
NSLog(@"play");
}
//方案1 (动态方法解析, 添加方法)(添加方法成功后,直接调用添加的方法,否则走->方案2消息转发))
//+ (BOOL)resolveClassMethod:(SEL)sel {
// if (sel == @selector(test)) {
// // 第一个参数是object_getClass(self) 元类对象
// class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
// return YES;
// }
// return [super resolveClassMethod:sel];
//}
+(BOOL)resolveInstanceMethod:(SEL)sel{
class_addMethod([self class], sel, class_getMethodImplementation([Student class], @selector(resolvePlay:)), method_getTypeEncoding(class_getInstanceMethod([Student class], @selector(resolvePlay:))));
return YES;
}
//方案2 (消息转发)(返回调用方法的target,直接target调用方法,否则走->方案3消息转发调用)
- (id)forwardingTargetForSelector:(SEL)aSelector{
return [Student new];
}
//方案3 (方案三的两个方法 方法签名&转发调用)
//方法签名(方法签名不为nil则走转发调用)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
//关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
//转发调用
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[Student new]];
}
补救方案
首先, 调用方法时, 系统会查看这个对象能否接收这个消息 (查看这个类有没有这个方法, 或者有没有实现这个方法。)
如果不能, 就会调用下面这几个方法, 给你“补救”的机会, 你可以先理解为几套防止程序crash的备选方案, 我们就是利用这几个方案进行消息转发;
注意, 前一套方案实现, 后一套方法就不会执行。
如果这几套方案你都没有做处理, 那么 objc_msgSend() 最后找不到合适的方法进行调用, 会报错 unrecognized selector sent to instance
方案1 (动态方法解析, 添加方法):
// 首先,系统会调用resolveInstanceMethod或resolveClassMethod 让你自己为这个方法动态增加实现。动态添加方法
- (BOOL)resolveInstanceMethod:(SEL)sel;
- (BOOL)resolveClassMethod:(SEL)sel;
方案2 (消息转发):
// 如果不对resolveInstanceMethod做任何处理, 系统会来到方案二, 走forwardingTargetForSelector方法,我们可以返回其他实例对象, 实现消息转发。
- (id)forwardingTargetForSelector:(SEL)aSelector;
方案3 (消息转发调用):
// 如果不实现 forwardingTargetForSelector, 系统就会调用方案三的两个方法 方法签名&转发调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
![](https://img.haomeiwen.com/i9612370/c4573c7ca5a13141.png)
![](https://img.haomeiwen.com/i9612370/a2e00b4be82dd61a.png)
![](https://img.haomeiwen.com/i9612370/82a8d91bd0728204.png)
作为抽象类不能直接使用 NSProxy
,它不提供初始化的方法,并且在接收到它没有响应的任何消息时引发异常,因此只能继承并在具体的子类提供初始化或创建方法,并实现 forwardInvocation:
和 methodSignatureForSelector:
所以其实只要实现了这两个方法即可,至于其它的一些方法,未实现均会走到消息转发这一步,若是实现其它譬如respondsToSelector:
也无妨
-
NSProxy
与NSObject
的消息传递的不同
NSObject
收到消息会先去缓存列表查找SEL,若是找不到,就到自身方法列表中查找,依然找不到就顺着继承链进行查找,依然找不到的话,那就是unknown selector
,进入消息转发程序
1.+(BOOL)resolveInstanceMethod:
其返回值为Boolean类型,表示这个类是否通过class_addMethod
新增一个实例方法用以处理该unknown selector
,也就是说在这里可以新增一个处理unknown selector
的方法,若不能,则继续往下传递(若unknown selector
是类方法,那么调用+(BOOL)resolveClassMethod:
)
2.- (id)forwardingTargetForSelector:
这是第二次机会进行处理unknown selector
,即转移给一个能处理unknown selector
的其它对象,若返回一个其它的执行对象,那消息从id objc_msgSend ( id self, SEL op, ...)
重新开始,若不能,则返回nil
,并继续向下传递,最后的一次消息处理机会(3 与 4 配套)
3.- (NSMethodSignature *)methodSignatureForSelector:
返回携带参数类型、返回值类型和长度等的selector
签名信息NSMethodSignature
对象,Runtime
内部会基于NSMethodSignature
实例构建一个NSInvocation
对象,作为回调- (void)forwardInvocation:
的入参
4.- (void)forwardInvocation:
这一步可以对传进来的NSInvocation
进行一些操作,它主要在对象之间或者应用程序之间存储和转发消息(命令模式的实现),灵活性很高,譬如修改target
、参数、甚至返回值,有兴趣可以去了解下NSInvocation
对于NSProxy
就没有这么复杂了,接收到unknown selector
后,直接回调- (NSMethodSignature *)methodSignatureForSelector:
和- (void)forwardInvocation:
, 消息转发过程简单的很多- 基于以上,实现上也很简单,就是将具体接收消息的实际对象保存在容器中,并按顺序让它们接收消息即可
/**
Normal forwarding 的第一步,也是消息转发的最后一次机会--这个针对NSObject, 对于 NSProxy 未实现立马走这里
消息获得函数的参数和返回值类型,即返回一个函数签名
@param sel selector 方法选择子
@return NSMethodSignature 函数签名
返回nil,Runtime 则会发出 -doesNotRecognizeSelector: 消息,程序 crash
返回了NSMethodSignature,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象
*/
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
for (id obj in self.targets) {
if ([obj respondsToSelector:sel]) {
return [obj methodSignatureForSelector:sel];
}
}
return [super methodSignatureForSelector:sel];
}
/**
可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一新的目标执行
@param invocation 对一个消息的描述,包括 selector 以及参数等信息
*/
- (void)forwardInvocation:(NSInvocation *)invocation {
BOOL invoked = NO;
for (id obj in self.targets) {
if ([obj respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:obj];
invoked = YES;
}
}
if (!invoked) {
[super forwardInvocation:invocation];
}
}
OC中存在这么一个默默无闻的类NSProxy,填补了"多继承"这个空白区.
网友评论