NSObject的消息转发的方法
//接受到无法解读的 类方法消息 时调用
+ (BOOL)resolveClassMethod:(SEL)sel ;
//接受到无法解读的 实例方法的消息 时调用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;
//备授接受者
- (id)forwardingTargetForSelector:(SEL)aSelector;
//转发消息
- (void)forwardInvocation:(NSInvocation *)anInvocation;
//方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//无法找到方法时调用
- (void)doesNotRecognizeSelector:(SEL)aSelector;
消息转发全流程
1.动态方法解析
//接受到无法解读的 类方法消息 时调用
+ (BOOL)resolveClassMethod:(SEL)sel ;
//接受到无法解读的 实例方法的消息 时调用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;
2.备援接受者
倘若没有动态新增方法来响应该选择子,则该对象会检查是否有其他对象来处理这条消息,也就是执行forwardingTargetForSelector:
方法寻找备授接受者。
3.完整的消息转发机制
- 若
forwardingTargetForSelector:
返回nil,没有其他对象处理该消息,则运行期系统会启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。 - 调用
- (void)forwardInvocation:(NSInvocation *)anInvocation
,在调用forwardInvocation:
之前会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法来获取这个选择子的方法签名,然后在-(void)forwardInvocation:(NSInvocation *)anInvocation
方法中你就可以通过anInvocation
拿到相应信息做处理。
4.抛出异常
若最终仍无法处理该消息,那么会调用NSObject的- (void)doesNotRecognizeSelector:(SEL)aSelector
方法,抛出异常。
5.接收者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大。
最好能在第一步就处理完,这样的话,运行期系统就可以将此方法缓存起来了。如果这个类的实例稍后还收到同名选择子,那么根本无须启动消息转发流程。
若想在第三步里把消息转给备援的接收者,那还不如把转发操作提前到第二步。因为第三步只是修改了调用目标,这项改动放在第二步执行会更为简单,不然的话,还得创建并处理完整的NSInvocation。
//
// YYWeakProxy.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 14/10/18.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYWeakProxy.h"
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
6.NSProxy
NSProxy没有父类,是顶级类(根类),跟NSObject同等地位。
NSProxy和NSObject都实现了“NSObject Protocol”。
NSProxy设计时就是以“抽象类”设计的,专门为转发消息而生。
继承NSProxy的子类要实现自己的初始化方法,如
init
、initWith
。继承NSProxy的子类需重写
- forwardInvocation:
和- methodSignatureForSelector:
方法,完成消息转发。
/* NSProxy.h
Copyright (c) 1994-2017, Apple Inc. All rights reserved.
*/
#import <Foundation/NSObject.h>
@class NSMethodSignature, NSInvocation;
NS_ASSUME_NONNULL_BEGIN
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
Class isa;
}
+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference NS_UNAVAILABLE;
- (BOOL)retainWeakReference NS_UNAVAILABLE;
// - (id)forwardingTargetForSelector:(SEL)aSelector;
@end
NS_ASSUME_NONNULL_END
要点
若对象无法响应某个选择子,则进入消息转发流程。
通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
对象可以把其无法解读的某些选择子转交给其他对象来处理。
经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。