动态消息转发机制实例

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:消息查看Selector对应的方法签名,即参数与返回值的类型信息。如果有方法签名返回,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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值