OC方法调用之objc_msgSend动态解析(续)
2024-01-12 00:38:46
上篇内容中,我们只关注了慢速查找的流程,至于慢速查找也没找到的情况还没有分析。
我们来看一下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查找方法,如果两个都没有找到的话,就往父类中查找,直到找到位置,或者没有父类了。