iOS DevcodeER.teciOS进阶之runtime

OC 消息转发机制

2016-08-19  本文已影响776人  心至靜行至遠

首先我们看一下objc_msgSend它具体是如何发送消息:

  1. 首先根据receiver对象的isa指针获取它对应的class
  2. 优先在class的cache查找message方法,如果找不到,再到
    methodLists查找
  3. 如果没有在class找到,再到super_class查找
  4. 一旦找到message这个方法,再依据receiver 中的self 指针找到当前的对象,调用当前对象的具体实现的方法(IMP),然后传递参数,调用实现方法。


    图1

    当对象收到无法解读的消息后,就会启动“消息转发”(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

消息转发全流程图:

图2

方案一:

+ (BOOL)resolveInstanceMethod:(SEL)sel

+ (BOOL)resolveClassMethod:(SEL)sel

首先,系统会调用resolveInstanceMethod(当然,如果这个方法是一个类方法,就会调用resolveClassMethod)让你自己为这个方法增加实现。

demo1:

定义一个Person,创建了一个Person类的对象p,然后调用p的run方法,注意,这个run方法是没有写实现的。

Person *p = [Person alloc] init];
[p run];

进入Person类的.m文件,我实现了resolveInstanceMethod这个方法为我的Person类动态增加了一个run方法的实现。

void run (id self, SEL _cmd) {

    NSLog(@"跑");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if(sel == @selector(run)){
        class_addMethod(self, sel, (IMP)run, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

方案二:

- (id)forwardingTargetForSelector:(SEL)aSelector

第二套方法,forwardingTargetForSelector,这个方法返回你需要转发消息的对象。

demo2:

为了便于演示消息转发,我们新建了一个汽车类Car,并且实现了Car的run方法。

现在我不去对方案一的resolveInstanceMethod做任何处理,直接调用父类方法。可以看到,系统已经来到了forwardingTargetForSelector方法,我们现在返回一个Car类的实例对象。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return [[Car alloc] init];  
}

继续运行,程序就来到了Car类的run方法,这样,我们就实现了消息转发。

方案三:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;

- (void)forwardInvocation:(NSInvocation *)invocation;

methodSignatureForSelector用来生成方法签名,这个签名就是给forwardInvocation中的参数NSInvocation调用的。

开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。

所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {

    NSString *sel = NSStringFromSelector(selector);
    if([sel isEqualString:@"run"]) {
        return [NSMethodSignature signatureWithObjcTypes:"v@:"];
    }
    return [sunper methodSignatureForSelector:selector];
}


- (void)forwardInvocation:(NSInvocation *)invocation {

    SEL selector = [invocation selector];
    //新建需要转发消息的对象
    Car *car = [[Car alloc] init];
    if([car respondsToSelector:selector]){
        [invocation invokeWithTarget:car];
    }
}

关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。

注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。

上一篇下一篇

猜你喜欢

热点阅读