美文网首页
三、方法(Method)的结构和调用

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

作者: LNG61 | 来源:发表于2018-05-23 19:19 被阅读0次

    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)转发

    相关文章

      网友评论

          本文标题:三、方法(Method)的结构和调用

          本文链接:https://www.haomeiwen.com/subject/gimydftx.html