向对象发送消息,如果没有在对象的模型上找到方法,系统会利用「消息转发」机制来动态处理这个问题
+(BOOL)resolveInstanceMethod:(SEL)sel;
对应实例方法+(BOOL)resolveClassMethod:(SEL)sel;
对应类方法- (id)forwardingTargetForSelector:(SEL)aSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
Human
, 继承自 NSObject
Adam
, 继承自 Human
Eve
, 继承自 Human
Gain
, 继承自 Human
Human
@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];
}
}