美文网首页
OC runtime 方法调用转发机制

OC runtime 方法调用转发机制

作者: 人话博客 | 来源:发表于2018-02-23 20:50 被阅读0次

消息发送的正常处理流程

  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *p = [Person new];
    // 调用 p 身上一个不存在的方法,就会进入消息转发
    SEL sel = NSSelectorFromString(@"caonima");
    [p performSelector:sel];
}
  1. 首先根据当前对象 reciver 对象的 isa 指针获取它的类型。
  2. 优先在 classcache 找到 message 方法,如果找不到,就去 methodList 方法列表里面去找。
  3. 如果在 class cache & methodList 都没有找到,会去 superClass 里面去找。
  4. 一旦找到了 message 方法,就执行该方法的 IMP
OC 方法搜索链条

消息的处理和转发流程(异常处理)

如果按照上述流程,仍然无法找到 message 的实现,消息就会进入转发阶段。(也就是在报 unrecognized selector sent to instance 0xxxxxx之前的阶段)

进入“异常处理”的阶段,也叫做消息转发

OC 消息转发流程

如果一个对象以及它的继承链条上都没有找到这个消息,那么就会进入到消息转发的阶段。

消息转发的第一个阶段:+ (BOOL)resolveInstanceMethod:(SEL)sel

当前 SEL 会转发给当前调用此方法的类。确认,自己是否真的无法完成此条消息的处理。
在这个方法中,返回一个 YES。告知系统,我可以处理这个消息。并动态的为此类添加这个方法。

//SEL 是结构体指针  IMP 是函数指针变量
void myMethodImp(id self,SEL _cmd) {
    NSLog(@"%@",@"我是 person 的方法实现");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selName = NSStringFromSelector(sel);
    if ([selName isEqualToString:@"caonima"]) {
        class_addMethod([self class], sel, (IMP)myMethodImp, "v@:");
        return YES;// 告诉外界,我可以执行这个 sel,你在返回去执行一次吧。
    }

    return NO; // 告诉,我无法执行这个 sel。消息转发进入到下一个步骤。
}

运行结果:

2017-10-14 17:27:52.548 CodeFor消息转发[81717:21305980] 我是 person 的方法实现

或者直接返回一个 nil,告知系统,我无法处理这个消息,你接着去下一个阶段吧
消息转发就进入到第二步:- (id)forwardingTargetForSelector:(SEL)aSelector

消息转发第二步:- (id)forwardingTargetForSelector:(SEL)aSelector

如果消息转发第一步返回 nil,那么就会进入到此步。

/**
 告诉,系统,Fahter可以执行这个方法
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *strSel = NSStringFromSelector(aSelector);
    if ([strSel isEqualToString:@"caonima"]) {
        return [Father new];
    }

    return nil;// 告诉系统,我也无法找到一个可以执行此方法的对象
}

运行结果:

2017-10-14 17:38:01.768 CodeFor消息转发[81937:21345385] 我是 father,我可以执行 caonima 这个消息

这一步的意义:作为接收消息的我来说,无法处理这个消息。但是我知道谁可以处理。

当然,如果当前我也不知道,谁可以处理这个消息,那么直接返回 nil
消息转发将会进入到第三步骤。

消息转发的第三步:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector & - (void)forwardInvocation:(NSInvocation *)anInvocation

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
动态的拼接一个Objc 方法的签名。如果返回 nil 或者一个错误的方法签名,将执行 doesNotRecognizeSelector

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"%@",@"没有返回一个正确的方法签名!");
    [super doesNotRecognizeSelector:aSelector];
}

2017-10-14 17:58:07.296 CodeFor消息转发[82176:21394061] 没有返回一个正确的方法签名!
2017-10-14 17:58:07.296 CodeFor消息转发[82176:21394061] -[Person caonima]: unrecognized selector sent to instance 0x618000009720

- (void)forwardInvocation:(NSInvocation *)anInvocation
动态的创建一个符合这个签名的方法,然后执行。

消息转发的第三步可以这么理解:

既然我自己无法处理这个消息,也不知道谁可以处理这个消息,那么我就自己创造一个可以处理这个消息的 NSInvocation.

一张更为清晰的消息转发机制流程图

关于 NSInvocation 如何自己创造一个对象方法来处理消息转发

苹果官方给的解释是:NSInvocation 作为对象呈现的 Objective-C 消息。

An Objective-C message rendered as an object.
NSInvocation objects are used to store and forward message between objects and between applications,
primarily by NSTimer objects and the distributed objects system.
An NSInvocation object contains all the elements of an Objective-C message: a target ,a selector, arguments , and the return value. Each of these elements can be set directly , and the return value is set automatically when the NSInvocation object is dispatched.

一个 NSInvocation 对象包含一个 Objective-C 消息的所有元素:target、selector、arguments、returnValue。可以直接设置每个元素,并在 NSInvocation 分派时,自动设置返回值。

// 一个完整方法OC方法调用
NSString *returnValue = [p runWithDestination:@"北极熊"];
一个完整的方法调用结构
  1. target
    2.sel
    3.arguments
    4.returnValue

使用消息转发的代码描述则是:

NSString *value = ((id(*)(id,SEL,id))objc_msgSend)(p,sel_registerName("runWithDestination:"),@"北极熊");
NSLog(@"%

OC方法和 C 消息发送对比

OC方法和 C 消息发送对比

相关文章

网友评论

      本文标题:OC runtime 方法调用转发机制

      本文链接:https://www.haomeiwen.com/subject/dkffxftx.html