消息传递与转发笔记

消息传递

[object doSomething]被编译器转化为

id objc_msgSend ( id self, SEL op, ... );

在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。

objc_msgSend 相关函数伪代码:

id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //调用这个函数,伪代码...
}

//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发
    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }

    Class curClass = cls;
    IMP imp = nil;
    do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);

    return imp;
}

class_getMethodImplementation 寻找IMP过程:

  • 先从当前class的cache方法列表(cache methodLists)里去找
  • 找到了,跳到对应函数实现
  • 没找到,就从class的方法列表(methodLists)里找
  • 还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止
  • 最后再找不到,就会调用_objc_msgForward函数,进行消息转发流程。

消息转发

_objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

1.调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。

2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。

3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。

4.调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。

5.调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的

也就是说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:

  • resolveInstanceMethod:方法 (或 resolveClassMethod:)。

  • forwardingTargetForSelector:方法

  • methodSignatureForSelector:方法

  • forwardInvocation:方法

  • doesNotRecognizeSelector: 方法

    消息转发调用过程.png

步骤1、2、3都是在没有实现msg方法的情况下,runtime为我们提供的补救的机会。
第一种:resolveInstanceMethod:

// c形式
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(say:)) {
        class_addMethod([self class], sel, (IMP)say, "v@:*");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void say(id self, SEL _cmd, NSString *str) {
    NSLog(@"Person say:%@", str);
}

//OC形式
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(say:)) {
        class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(sayMethodIMP:)), "v@:*");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)sayMethodIMP:(NSString *)str {
    NSLog(@"Person say:%@", str);
}

第二种:forwardingTargetForSelector:这里增加了Mobile类来相应say:消息。

//Mobile.h
#import 

@interface Mobile : NSObject
- (void)say:(NSString*)world;
@end

//Mobile.m
#import "Mobile.h"

@implementation Mobile
- (void)say:(NSString*)world {
    NSLog(@"Mobile say:%@", world);
}
@end

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(say:)) {
        return [Mobile new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

第三种:forwardInvocation:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(say:)) {
        return [NSMethodSignature signatureWithObjCTypes:"V@:*"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    Mobile *mobile = [Mobile new];
    if ([mobile respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:mobile];
    }
}

你可能感兴趣的:(消息传递与转发笔记)