向对象发送消息,如果没有在对象的模型上找到方法,系统会利用「消息转发」机制来动态处理这个问题
+(BOOL)resolveInstanceMethod:(SEL)sel; 对应实例方法+(BOOL)resolveClassMethod:(SEL)sel; 对应类方法- (id)forwardingTargetForSelector:(SEL)aSelector- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (void)forwardInvocation:(NSInvocation *)anInvocationHuman, 继承自 NSObjectAdam, 继承自 HumanEve, 继承自 HumanGain, 继承自 HumanHuman
@interface Human: NSObject
- (void)eat;
- (void)sleep;
@end
@implementation Human
- (void)eat {
NSLog(@"%@ eats", self);
}
- (void)sleep {
NSLog(@"%@ sleeps", self);
}
@end
Adam
@interface Adam: Human
- (void)work;
@end
@implementation Adam
- (void)work {
NSLog(@"%@ works", self);
}
@end
Eve
@interface Eve: Human
- (void)birth;
@end
@implementation Eve
- (void)birth {
NSLog(@"%@ births", self);
}
Gain
@interface Gain: Human
- (void)slay;
@end
@implementation Gain
- (void)slay {
NSLog(@"%@ slayed Abel", self);
}
@end
+(BOOL)resolveInstanceMethod:(SEL)sel; 对应实例方法+(BOOL)resolveClassMethod:(SEL)sel; 对应类方法动态解析,实际上还没有到达传说中的「消息转发」的地步,只是看能不能动态地给类添加方法,应用事例:使用 @dynamic 修饰的属性
sel 未能处理的 selector
BOOL 是否添加了新方法来处理 selector
Human *human = [[Human alloc] init];
[human eat];
[human sleep];
Adam *adam = [[Adam alloc] init];
[adam eat];
[adam sleep];
[adam work];
// 向 human 发送 work 消息,不进行方法动态解析的情况下,这会崩溃
[human performSelector:@selector(work)];
添加重定向的方法
void workSomething(id self, SEL _cmd) {
NSLog(@"method resolved");
NSLog(@"class: %@, selector: %@", self, NSStringFromSelector(_cmd));
// NSLog(@"class: %@, selector: %s", self, sel_getName(_cmd)); // 与上一句效果一样
}
在 Human 上重写动态解析相关方法
#include <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selector = NSStringFromSelector(sel);
if ([selector isEqualToString:@"work"]) {
class_addMethod(self, sel, (IMP)workSomething, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
使用 Runtime 方法前,先引入这个文件 在这个例子中,class_addMethod 是 Runtime 的方法
- class_addMethod 方法最后的参数解析
- 每个方法默认隐藏两个参数:self -> 调用者,_cmd -> 方法的 SEL
v@:- v -> 函数返回值为 void
- @ -> self
- -> _cmd
不实现方法的动态解析,也可以直接消息转发
消息转发也有两种实现
- (id)forwardingTargetForSelector:(SEL)aSelector
aSelector 未能处理的 SEL
id 重新接收消息的对象
Human *human = [[Human alloc] init];
[human eat];
[human sleep];
Eve *eve= [[Eve alloc] init];
[eve eat];
[eve sleep];
[eve birth];
// 向 human 发送 birth 消息,没有实现消息转发的情况下,这会崩溃
[human performSelector:@selector(birth)];
在 Human 中添加实现消息转发的方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([Eve instancesRespondToSelector:aSelector]) {
return [[Eve alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
NSInvocation
NSMethodSignature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
NSInvocation, 返回方法签名对象- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (void)invokeWithTarget:(id)anObject;- (void)doesNotRecognizeSelector:(SEL)aSelector;
在「只替换消息转发对象」的环节中,失败之后,Runtime 将会调用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; 创建 NSInvocation. 然后调用 - (void)forwardInvocation:(NSInvocation *)anInvocation;, 在此方法中,会对新创建的 NSInvocation 调用 - (void)invokeWithTarget:(id)anObject; 方法,在这个方法中,会对新接收者(anObject)调用同名方法(那个未能处理的方法)
需要重写的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;- (void)forwardInvocation:(NSInvocation *)anInvocation;需要手动调用的方法
- (void)invokeWithTarget:(id)anObject;- (void)doesNotRecognizeSelector:(SEL)aSelector;Human *human = [[Human alloc] init];
[human eat];
[human sleep];
Gain *gain = [[Gain alloc] init];
[gain eat];
[gain sleep];
[gain slay];
// 向 human 发送 work 消息,不进行消息转发的情况下,这会崩溃
[human performSelector:@selector(slay)];
在 Human 中实现消息转发的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 先看看父类有没有进行消息转发
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([Gain instancesRespondToSelector:aSelector]) {
signature = [Gain instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
if ([Gain instancesRespondToSelector:sel]) {
Gain *gain = [[Gain alloc] init];
[anInvocation invokeWithTarget:gain];
} else {
[self doesNotRecognizeSelector:sel];
}
}