三、方法(Method)的结构和调用
Method的结构
// objc-runtime-new.h
struct method_t{
SEL name; // 方法名
const char *types; // 参数和返回类型encode
IMP imp; // 函数指针
};
typedef struct method_t *Method;
Method
的结构非常简单,只包括方法名、类型以及函数指针。那Method
又是存放在Class
中哪里的呢?这里通过一张图来说明:
Method
主要存放在class_rw_t
中的methods
和class-ro-t
中的baseMethodList
两个地方。
Method的初始化
了解了Method
的结构和存放地方之后,接下来说明下Method
是如何随着Class
的初始化而初始的,在经过一些断点调试之后,发现初始化的过程是在static Class realizeClass(Class cls)
中初始化的。
static Class realizeClass(Class cls){
const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw;
if(ro->flags & RO_FUTERE){
// rw已经分配好了
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING,RW_FUTURE);
}else{
// 未初始化
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
// 其它的一些初始化
// Attach categories
methodizeClass(cls);
}
static void methodizeClass(Class cls){
// 将ro中的基本方法添加到rw中去
method_list_t *list = cls->data()->ro->baseMethods();
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
cls->data()->methods.attachLists(&list, 1);
// 初始化属性、协议和分类
}
realizeClass
中主要是将rw
进行初始化,ro
的初始化过程则是在编译过程中已完成,无法看到对应的源码。之后我们通过打印自己的类来看一下这个初始化的过程(可以通过lldb
中的p
命令打印出结构体里的东西),首先我们新建了一个新类TestObject
:
@interface TestObject : NSObject
+ (void)testTaggedPointer;
+ (void)testObjc2Macro;
+ (void)testMetaClass;
@end
未初始化rw
首先我们来看下
TestObject
中的rw
未初始化时,ro
里的值有什么?ro
我们可以获取到
ro->baseMethods()
已经保存着类的三个基本方法了。接下来初始化rw
之后并methodizeClass
之后,这个时候ro
的基本方法应该已经拷贝到rw
中去了,我们来验证下:methodizeClass
rw
可以看到此时
rw
已经初始化完成了。
动态添加Method
class_addMethod
这个函数可以在运行期对类动态添加方法,我们来看下是怎么实现的:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types){
return !addMethod(cls, name, imp, types ?:"", NO);
}
static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace){
method_t *m;
IMP result;
if((m = getMethodNoSuper_nolock(cls, name)){
// 已经存在这个方法
if(!replace){
result = m->imp;
}else{
result = _method_setImplementation(cls, m, imp); // 重新设置imp
}
}else{
// 添加方法
method_list_t *newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->first.name = name;
newlist->first.types = types;
newlist->first.imp = imp;
prepareMethodsLists(cls, &newlist, 1, NO, NO);
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
result = nil;
}
return resul;
}
向一个类中添加新方法,cls->data()->methods.attachLists(&newlist, 1)
,仍然是添加到class_rw_t.methods
列表中。
Method的调用与查找
我们知道OC
中调用方法其实是发送消息,相当于调用objc_msgSend(id self, SEL op, ...)
,但objc_msgSend
不是开源的。但在上面动态添加方法addMethod
中,可以看到首先通过getMethodNoSuper_nolock
查找这个方法是否存在,我们可以猜测这个方法必定会在调用方法过程来调用,毕竟要先查询才能调用。通过查找调用getMethodNoSuper_nolock
这个方法的地方和加断点,可以查到objc_msgSend
发送消息后,接着调用IMP lookUpImpOrForward(Class cls, SEL sel, bool initialize, bool cache, bool resolver)
查找方法并调用。
主要有以下步骤
1.查找缓存
IMP imp = cache_getImp(cls, sel);
if(imp) return imp;
如果缓存中已缓存sel
,那么直接返回对应的imp
。
2.在当前类中查找Method
Method meth = getMethodNoSuper_nolock(cls, sel);
if(meth){
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
return meth->imp;
}
从当前类中找出sel
对应的method
,如果找到了则缓存起来并返回。下面是在当前类查找方法的实现:
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel){
auto mlists = cls->data()->methods.beginLists();
auto end= cls->data()->methods.beginLists();
for(;mlists != end; ++mlists){
method *m = search_method_list(*mlists, sel);
if(m) return m;
}
return nil;
}
查找过程非常简单,则列表中遍历查询。
3.在父类中查找Method
如果在当前类中没有找到该方法对应的实现,那么将在继承链中查找这个方法。
for(Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass){
// cache
IMP imp = cache_getImp(curClass, sel);
if(imp) return imp;
// method list
Method meth = getMethodNoSuper_nolock(curClass , sel);
if(meth){
log_and_fill_cache(curClass , meth->imp, sel, inst, curClass );
return meth->imp;
}
}
4.方法决议
如果在继承链中都无法找到该方法,那么将该方法决议(method resolve)。
_class_resolveMethod(cls, sel, inst);
void _class_resolveMethod(Class cls, SEL sel, id inst){
if(!cls->isMetaClass()){
_class_resolveInstanceMethod(cls, sel, inst);
}else{
_class_resolveClassMethod(cls, sel, inst);
if(!lookUpImpOrNil(cls, sel, inst, NO, YES, NO){
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
5.转发
最后一步是将进行转发,并且缓存。
IMP imp = (IMP)objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
小结
- 编译期间的方法存放在
class_ro_t.baseMethods
中,realizeClass
中初始化cls.data()
后,类的方法存放在class_rw_t.methods
中。 - 运行期动态添加的方法也是添加到
class_rw_t.methods
中。 - 方法的查找步骤:
(1)查找缓存
(2)在当前类中查找Method
(3)在父类中查找Method
(4)方法决议
(5)转发