Obective-C是由smalltalk演化而来,方法的调用实际上是消息的传递。这篇通过实例来证明动态动态消息转发机制
在Xcode中新建一个Command-Line Tool工程,在工程中New File一个Person类
在Person类中添加如下代码:
@interface Person : NSObject
- (void)run;
@end
@implementation Person
- (void)run
{
NSLog(@"Person run");
}
@end
在main.m
主程序中添加如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];
}
return 0;
}
运行程序可以在控制台打印出
Person run
该过程实际的操作是向person对象发送run的消息。如果person无法响应run方法的话就会抛出如下错误:
* Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[Person run]: unrecognized selector sent to instance
然而从person开始寻找run方法到抛出错误其实是经过了一个很『漫长』的过程。因为当某个类无法响应一个消息的时候,Objective-C会给你补救的机会。有如下三种补救的方式
-
方法一
+(BOOL)resolveInstanceMethod:(SEL)sel // 挽救实例方法
+(BOOL)resolveClassMethod:(SEL)sel // 挽救类方法
-
方法二
-(id)forwardingTargetForSelector:(SEL)aSelector
-
方法三
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
方法一
在Person类中将run
方法注释并加入如下代码:
/**
* 消息转发自己类
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)run, nil);
return YES;
}
return [super resolveInstanceMethod:sel];
}
void run (id self, SEL _cmd)
{
NSLog(@"%@, %s",self, sel_getName(_cmd));
}
运行程序,在未实现run
方法的情况下并没有崩溃而是在控制台有如下输出:
, run
该方法run
消息的响应还是在本类中。
方法二
该方法可以将消息转发到其他类中,为了演示我们新建一个Car类
在Car类中实现如下方法:
- (void)run
{
NSLog(@"Car run");
}
我们在本类中添加forwardingTargetForSelector
指明要转发到的类:
/**
* 消息转发它类
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [[Car alloc] init];
}
运行程序可以在控制台看到有如下输出:
RunTimeTest[47071:7072728] Car run
person
成功的将run
消息转发给了Car类
方法三
首先runtime发送methodSignatureForSelector:
消息查看Selecto
r对应的方法签名,即参数与返回值的类型信息。如果有方法签名返回,runtime则根据方法签名创建描述该消息的NSInvocation
,向当前对象发送forwardInvocation:
消息,以创建的NSInvocation对象作为参数;若methodSignatureForSelector:
无方法签名返回,则向当前对象发送doesNotRecognizeSelector:
消息,程序抛出异常退出。
我们在Person中添加如下两个方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel isEqualToString:@"run"]) {
NSString *string = @"v@:";
const char *types = [string UTF8String];
return [NSMethodSignature signatureWithObjCTypes:types];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
Car *car = [[Car alloc] init];
if ([car respondsToSelector:selector]) {
[anInvocation invokeWithTarget:car];
}
}
运行程序可以在控制台看到有如下输出:
RunTimeTest[47071:7072728] Car run
说明Person类成功的转发了消息。
关于生成签名的类型”v@:”解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。