在OC方法调用过程当中,当无法响应一个selector时,会触发消息转发机制。
在触发消息转发机制之前,Runtime提供了两个轻量的方法来处理这个selector
resolveInstanceMethod
该方法主要是当前类为无法响应的selector提供方法实现(IMP)的机会。
void dynamicMethodIMP(id self, SEL _cmd){/*...implementation...*/}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
如果该selector是类方法,则使用resolveClassMethod
。
如果resolveInstanceMethod
返回NO,则说明该类无法为这个selector提供方法实现,此时将会调用forwordingTargetForSelector
。
forwordingTargetForSelector
这个方法简单的将selector转发给其他对象来处理。
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
return self.myObj;
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
forwardInvocation
如果上面两个方法都无法处理这个selector,则会触发消息转发机制
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
methodSignatureForSelector
尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
NSMethodSignature
用来表示方法的签名信息:返回值,参数数量和类型。
NSInvocation包含完整的消息,SEL + 执行SEL的Target + 参数值
forwardInvocation我们可以做以下事情:
- 修改SEL的名字
- 修改执行SEL的Target
- 修改参数
如果forwardInvocation还不能处理这个selector,就会直接调用doesNotRecognizeSelector抛出异常。
下面是改变选择子的例子,比如我们直接调用的是playPiano方法,最后转发给了traval:方法:
// 完整的消息转发
- (void)travel:(NSString*)city
{
NSLog(@"Teacher travel:%@", city);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *method = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:method]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return signature;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = @selector(travel:);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
anInvocation = [NSInvocation invocationWithMethodSignature:signature];
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(travel:)];
NSString *city = @"北京";
// 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
[anInvocation setArgument:&city atIndex:2];
if ([self respondsToSelector:sel]) {
[anInvocation invokeWithTarget:self];
return;
} else {
Student *s = [[Student alloc] init];
if ([s respondsToSelector:sel]) {
[anInvocation invokeWithTarget:s];
return;
}
}
// 从继承树中查找
[super forwardInvocation:anInvocation];
}
网友评论