runtime的消息发送和转发机制

Runtime消息发送和消息转发机制

方法的本质

方法的本质是消息

消息:1:消息接受者 2:消息主体

快速查找

objc_msgSend

在OC中,所有的消息调用最后都会通过 objc_msgSend 方法进行访问。通过 objc_msgSend 进行消息调用,为了加快执行速度,这个方法在runtime源码中是用汇编实现的。

调用方法的时候底层会objc_msgSend查找方法的imp

发送消息的是一个对象。如果要找类对象需要通过对象的isa指针查找到类对象。

objc_msgSend:发送消息。 isa-->消息的接受者-->类(对象方法)或元类(类方法)

万物皆对象 类也是对象 objc_class继承objc_object

isa是一个isa_t公用体,里面有很多字段。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

方法有方法缓存

方法缓存存在cache_t中,cache_t存在类对象objc_class结构体中。

在objc_class结构体中根据偏移计算相应结构体指针。

cache_t是一个结构体 里面有一个存储方法缓存的bucket_t数组,bucket_t里面有_imp,_sel。

根据sel和imp在方法缓存中,如果缓存中找不到(缓存没有命中),则来到__objc_msgSend_uncached方法中进行没有方法缓存的查找,才从methodList中找(遍历方法列表)。

总结

objc_msgSend在查找的时候先进行一个缓存的命中,通过地址的与运算,拿到当前的缓存,和当前要查找的sel进行比对,如果比对上就命中了,直接使用。没有比对上则进行__objc_msgSend_uncached。

__objc_msgSend_uncached

没有查找到缓存的时候,需要遍历方法列表。lookUpImpOrForward。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){} 从汇编层面到C++层面

慢速查找

lookUpImpOrForward

缓存没有就去找methodList,从类的data_bits中的data数据中找(data数据中有 属性列表 成员变量 方法列表 协议列表)。

lookUpImpOrForward :methodList 自己找不到 找到getSuperClass 接着循环找。

调用 lookUpImpOrForward 方法,返回值是个 IMP 指针,如果查找到了调用函数的 IMP ,则进行方法的访问

  1. 当前类对象的方法列表中遍历方法列表。
  2. 沿着当前继承链当中的superClass,指针的指向,来进行方法遍历查找。
  3. 一直遍历到Root Class(NSObject)
  4. 找到之后,就存到cache中,下次就是快速查找,不用再慢速查找了。

快速查找流程和慢速查找流程都没有找到,则进行动态方法解析

消息转发机制 动态特性

如果没有查到对于方法的 IMP 指针,则进行消息转发机制

_objc_msgForward消息转发做的几件事

1. 动态方法解析

resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)

拦截处理。add_method 重新给实例对象(类对象)添加方法

里面有两个方法

resolveInstanceMethod(inst, sel, cls);

resolveClassMethod(inst, sel, cls);

调用 resolveInstanceMethod:resolveClassMethod:,允许用户在此时为该Class动态添加实现。如果实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。本质上是在方法列表中建立SEL和新的IMP关系。新的IMP是自己做的。如果仍没实现,继续下面的动作。

2. 消息转发流程

快速转发 慢速转发

a. 快速转发

重定向 forward(备用接收者阶段,obj_msgForward 方法的转发)找一个备用的接收者来处理消息。

如果第一层转发返回 NO ,则会进行第二层转发,调用forwardingTargetForSelector:,可以把调用转发到另一个对象,这是类级别的转发,调用另一个类的相同的方法。

注意:这里不要返回self,否则会形成死循环。

b. 慢速转发

(标准消息转发阶段,obj_msgForward 方法的转发)爱咋咋地,消息拦截了,自己处理。

如果上面转发返回 nil ,则会进入这一层处理

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

调用forwardInvocation:方法,将上面获取到的方法签名包装成Invocation传入,如何处理就在这里面了,并返回nil。

通过方法签名锁定forwardInvocation 谁能处理谁处理

Invocation会绑定 target aseletor.

这次是完整的消息转发,因为你可以返回方法签名、动态指定调用方法的Target

好处:

还可以继续重定向,还可以修改方法主体。操作的可自由度高。

例:aspect 切面

c. 如果转发都失败

调用doesNotRecognizeSelector:,crash抛出异常。


总结

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

  1. resolveInstanceMethod:(resolveClassMethod:)

    add method

  2. forwardingTargetForSelector:

    找一个备用接受者

  3. methodSignatureForSelector: 和 forwardInvocation:

    完整消息转发

  4. doesNotRecognizeSelector:

一旦调用_objc_msgForward将会跳过查找IMP的过程,直接触发消息转发。

forwardInvocation和forwardingTargetForSelector的区别

forwardingTargetForSelector只能转一个

forwardInvocation可以转多个

你可能感兴趣的