三、方法(Method)的结构和调用

2018-05-23  本文已影响0人  LNG61

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的存放
Method主要存放在class_rw_t中的methodsclass-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)查找方法并调用。

lookUpImpOrForward
主要有以下步骤

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);

小结

  1. 编译期间的方法存放在class_ro_t.baseMethods中,realizeClass中初始化cls.data()后,类的方法存放在class_rw_t.methods中。
  2. 运行期动态添加的方法也是添加到class_rw_t.methods中。
  3. 方法的查找步骤:
    (1)查找缓存
    (2)在当前类中查找Method
    (3)在父类中查找Method
    (4)方法决议
    (5)转发
上一篇下一篇

猜你喜欢

热点阅读