返回

OC方法调用之objc_msgSend动态解析(续)

IOS

上篇内容中,我们只关注了慢速查找的流程,至于慢速查找也没找到的情况还没有分析。

我们来看一下behavior的值是什么,他是作为参数被传递给method_lookup_helper,从而给他的第一个值赋上一个枚举值。

typedef enum MethodLookupBehavior
{
    kMethodLookupBehaviorAllowHidden = (1 << 0),
    kMethodLookupBehaviorAllowPrivate = (1 << 1),
} MethodLookupBehavior;

behavior的值可能的枚举值有kMethodLookupBehaviorAllowHidden和kMethodLookupBehaviorAllowPrivate,它的作用是判断查找出来的函数属性是否符合要求,如果不符合要求的话,函数的属性会被剥离。

关于函数属性在OC中的定义如下:

typedef uint32_t IMPEncoding;

typedef struct objc_method {
    IMPEncoding imp;
    SEL sel;
    uint8_t types;
} objc_method;

我们可以看到属性结构体中有IMPEncoding和types。其中IMPEncoding是 函数的实现,而types则是函数的。

IMPEncoding是由objc_msgSend中的两个参数获取到的,然后IMPEncoding中的数据是objc_copyMethodList获取的,IMPEncoding中的数据会给IMP赋值。

IMPEncoding是一个结构体,结构体里有三个成员变量,分别是_class、_sel、_imp。

struct IMPEncoding {
    Class _class;
    SEL _sel;
    IMP _imp;
};

IMPEncoding中的IMP属性的定义如下:

typedef id (*IMP)(id receiver, SEL sel, ...);

IMP的IMPEncoding的结构体类型,参数是receiver,sel和..

关于receiver和sel

receiver是动态解析时需要用到的,是一个实例化的类对象。

sel则是指向函数名称的指针。

typedef struct objc_selector *SEL;

IMPEncoding的_imp对应IMP结构体成员变量,而它的类型是id (*IMP)(id receiver, SEL sel, ...) ,参数receiver和sel我们已经解释过了。后面多出来的…说明IMPEncoding可以接受0到多个可选参数。

IMPEncoding的_class表示的则是函数所在类的类型,但是_class并不是具体的实例化后的类对象,而是指向函数所属类的指针。

IMPEncoding的_sel类型是SEL,它是一个指向函数名称的指针。

最后IMPEncoding里的类型(types),它对应的是函数属性。

struct objc_method {
    IMPEncoding imp;
    SEL sel;
    uint8_t types;
} objc_method;

函数的属性中存储的是函数的返回类型和参数类型,这些属性可以使用objc_property_t类型去获取。

struct objc_property_t {
    const char *name;
    const char *attributes;
};

其中name和attributes是用来存储返回类型和函数的参数类型。

关于函数属性的获取,可以使用以下函数:

IMP method_getImplementation(Method m);
Method method_setImplementation(Method m, IMP imp);
const char *method_getTypeEncoding(Method m);
Method method_setReturnType(Method m, const char *types);

类型的可以分为返回类型和参数类型,他们是由":"隔开的,在":"的前面是返回类型,在":"的后面则是参数类型,参数类型有多个时,他们之间是使用","进行分割的。

下面我们来分析method_lookup_helper,他的原型是

static Method method_lookup_helper(Class cls, SEL name, MethodLookupBehavior behavior)

他会调用method_lookup方法,他的原型如下:

static Method method_lookup(Class cls, SEL name, MethodLookupBehavior behavior)

method_lookup方法执行后的流程根据行为来决定, 如果behavior是kMethodLookupBehaviorAllowHidden,这个方法会调用objc_lookup_method方法。他的原型如下:

Method objc_lookup_method(Class cls, SEL name)

这个方法是在运行时用来动态绑定方法的。他的作用是, 在objc_msgSend执行时如果消息的接收者类中没有找到对应的方法,那么objc_msgSend就会调用objc_lookup_method方法来查找该消息在哪个类中。

如果behavior是kMethodLookupBehaviorAllowPrivate,这个方法会调用class_getInstanceMethod方法。他的原型如下:

Method class_getInstanceMethod(Class cls, SEL name)

这个方法跟上面的方法类似,都是用在objc_msgSend执行时动态绑定的方法,不过跟objc_lookup_method不同的是,class_getInstanceMethod只查找一个类中的方法,不会去其父类中找。

至于objc_msgSend中对于方法的查找流程,就需要比较上面两个方法的执行后的流程,如果都找不到的话,他就会往接收者的父类找,直到找到位置,或者没有父类了。

动态解析查找流程就到这里,通过method_lookup_helper我们知道,如果执行动态解析的话,先执行objc_lookup_method查找方法,然后再执行class_getInstanceMethod查找方法,如果两个都没有找到的话,就往父类中查找,直到找到位置,或者没有父类了。