iOS 中消息发送与转发

objc_msgSend

调用一个方法的时候,runtime 层会将这个调用翻译成

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

比如,一条语句 [receiver message]; 会由编译器转化为以下的纯 C 调用:

objc_msgSend(receiver, @selector(message));

objc_msgSend 的伪代码为:

id objc_msgSend(id self, SEL _cmd, ...) {
    Class c = object_getClass(self);
    IMP imp = cache_lookup(c, _cmd);
    if(!imp)
        imp = class_getMethodImplementation(c, _cmd);
    return imp(self, _cmd, ...);
}

当向一般对象发送消息时,调用 objc_msgSend;当向 super 发送消息时,调用的是 objc_msgSendSuper; 如果返回值是一个结构体,则会调用 objc_msgSend_stretobjc_msgSendSuper_stret

消息分发

objc_msgSend 的消息分发分为以下几个步骤:

  • 判断 receiver 是否为 nil,也就是 objc_msgSend 的第一个参数 self,也就是要调用的那个方法所属对象
  • 从缓存里寻找,找到了则分发,否则
  • 利用 objc-class.mm_class_lookupMethodAndLoadCache3 方法去寻找 selector
    • 如果支持 GC,忽略掉非 GC 环境的方法(retain 等)
    • 从本 classmethod list 寻找 selector,如果找到,填充到缓存中,并返回 selector,否则
    • 寻找父类的 method list,并依次往上寻找,直到找到 selector,填充到缓存中,并返回 selector,否则
    • 调用 _class_resolveMethod,如果可以动态 resolve 一个 selector,不缓存,方法返回,否则
    • 转发这个 selector,否则
  • 报错,抛出异常

类的定义里就有 cache 字段,类的所有缓存都存在 metaclass 上,所以每个类都只有一份方法缓存,而不是每一个类的 object 都保存一份。
即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的 metaclass 里缓存一份。
在调用 _class_lookupMethodAndLoadCache3 之前,已经是从缓存无法找到 selector 了,所以这个方法避免了再去扫描缓存查找方法的过程,而是直接从方法列表找起。

动态添加方法

允许用户在此时为该 Class 动态添加实现。

void _class_resolveMethod(Class cls, SEL sel, id inst)

函数原码为:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

此方法是动态方法解析的入口,会间接地发送 +resolveInstanceMethod+resolveClassMethod 消息。通过对 isa 指向的判断,从而分辨出如果是对象方法,则进入 +resolveInstanceMethod 方法,如果是类方法,则进入 +resolveClassMethod 方法。

动态添加实例方法:

void dynamicInstanceMethod(id self, SEL _cmd) {
    NSLog(@" >> dynamic Instance Method");
}
@implementation TestMessage
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(noneMethod))
    {
        class_addMethod([self class], sel, (IMP)dynamicInstanceMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

动态添加类方法:

void dynamicClassMethod(id self, SEL _cmd){
    NSLog(@" >> dynamic Class Method");
}
@implementation TestMessage
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(noneMethod)) {
        Class metaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
        class_addMethod(metaClass, @selector(noneMethod), (IMP)dynamicClassMethod, "v@:");
        return YES;
    }
    else{
        return [super resolveClassMethod:sel];
    }
}
@end

注意类方法需要添加到 Meta Class 中。关于 classmeta class 的关系可以参考下图:

消息转发机制(message forwarding)

当对象接受到无法解读的消息后,就会启动消息转发机制。当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。

消息转发分为两个阶段

  • 先征询接收者所属都类,看其是否能动态添加方法
  • 完整的消息转发机制
    • 查看是否有备援接收者 replacement receiver,否则
    • 把消息的全部细节封装到 NSInvocation 对象中,再给接收者最后一次机会

备援接收者

尝试找到一个能响应该消息的对象。

- (id)forwardingTargetForSelector:(SEL)selector;

完整的消息转发

先调用 methodSignatureForSelector: 方法,尝试获得一个方法签名。如果获取不到,则直接调用 doesNotRecognizeSelector 抛出异常。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

然后创建 NSInvocation 对象,调用

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

代码:

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

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

消息转发总结

非常值得读的文章

  • Objective-C 中的消息与消息转发
  • 神经病院 Objective-C Runtime 住院第二天——消息发送与转发
  • Objective-C 消息发送与转发机制原理
  • Objective-C Runtime之动态方法解析和消息转发

你可能感兴趣的:(iOS 中消息发送与转发)