美文网首页iOS底层
类(二)-- method归属

类(二)-- method归属

作者: 过气的程序员DZ | 来源:发表于2020-09-15 15:52 被阅读0次

    类(一)-- 底层探索
    类(二)-- method归属
    类(三)-- cache_t分析

    本文内容:

    1. OC方法中的sel和imp
    2. 类中method的归属以及一些api的实现
    3. isKindOfClass & isMemberOfClass

    1、OC方法中的sel和imp


    OC是对C/C++的超集(此处不是错别字,超级集合的含义),也可以说OC是一种上层封装。OC中方法调用,为了能够找到指定的方法实现,就得对方法做一个“标识”,然后通过这个“标识”去找到具体的实现位置。方法中的selimp就是为了实现这个“功能”。

    • sel:方法编号,也就是“标识”。
    • imp:方法实现的指针,通过它来找到具体方法实现的位置。

    可以把sel看做一本书目录中的一条信息,imp看做信息的页码。通过这条信息,找到指定页码,就可以翻到这页来查看相关内容。

    2、类中method的归属


    类中的实例方法(-方法)存在当前类中,类方法(+方法)存在元类中。其实是类的isa走位有关系,通过runtime的一些api可以进行确认。

    定义一个DZPerson类,类中分别定义了一个实例方法(sayHi)和一个类方法(say666):

    @interface DZPerson : NSObject
    - (void)sayHi;
    + (void)say666;
    @end
    
    @implementation DZPerson
    
    - (void)sayHi{
        NSLog(@"%s", __func__);
    }
    
    + (void)say666{
        NSLog(@"%s", __func__);
    }
    @end
    
    ⏬⏬⏬
    
    //调用
    DZPerson *per = [DZPerson alloc];
    Class pCla = object_getClass(per);
    Class metaCla = objc_getMetaClass("DZPerson");
    
    • 创建一个DZPerson的对象per
    • 获取per的类pCla
    • 再获取DZPerson的元类metaCla

    2.1 class_getInstanceMethod

    class_getInstanceMethod作用:获取类的实例方法

    2.1.1 示例:
    Method m1 = class_getInstanceMethod(pCla, @selector(sayHi));
    Method m2 = class_getInstanceMethod(metaCla, @selector(sayHi));
        
    Method m3 = class_getInstanceMethod(pCla, @selector(say666));
    Method m4 = class_getInstanceMethod(metaCla, @selector(say666));
    
    NSLog(@"m1:%p", m1);
    NSLog(@"m2:%p", m2);
    NSLog(@"m3:%p", m3);
    NSLog(@"m4:%p", m4);
    

    输出结果:


    • m1:DZPerson类中查找sayHi方法,sayHi是实例方法,所以可以找到。
    • m2:通过m1结果,sayHi存在类中,所以DZPerson元类中找不到sayHi,因此m2返回地址为NULL
    • m3&m4:同理,这次找的是类方法say666,存在元类中,所以m3没结果,m4有结果。

    接着看看源码中class_getInstanceMethod如何实现

    2.1.2 class_getInstanceMethod源码分析
    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    #warning fixme build and search caches
        lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    
    #warning fixme build and search caches
        return _class_getMethod(cls, sel);
    }
    
    ⏬⏬⏬
    
    static Method _class_getMethod(Class cls, SEL sel)
    {
        mutex_locker_t lock(runtimeLock);
        return getMethod_nolock(cls, sel);
    }
    
    ⏬⏬⏬
    
    static method_t *
    getMethod_nolock(Class cls, SEL sel)
    {
        method_t *m = nil;
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
    
        while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
            cls = cls->superclass;
        }
    
        return m;
    }
    
    ⏬⏬⏬
    
    static method_t *
    getMethodNoSuper_nolock(Class cls, SEL sel)
    {
        runtimeLock.assertLocked();
    
        ASSERT(cls->isRealized());
    
        auto const methods = cls->data()->methods();
        for (auto mlists = methods.beginLists(),
                  end = methods.endLists();
             mlists != end;
             ++mlists)
        {
            method_t *m = search_method_list_inline(*mlists, sel);
            if (m) return m;
        }
    
        return nil;
    }
    

    通过源码推导出的简要流程图:


    其中最核心的代码就是getMethodNoSuper_nolock函数中的auto const methods = cls->data()->methods();这行代码(图中红色部分)。从当前类中获取方法列表methods(),找到了直接返回;找不到再去父类中查找。

    通过源码分析,很容易清楚class_getInstanceMethod示例的运行结果。

    2.2 class_getClassMethod

    class_getClassMethod作用:获取类方法

    2.2.1 示例
    Method m5 = class_getClassMethod(pCla, @selector(sayHi));
    Method m6 = class_getClassMethod(metaCla, @selector(sayHi));
        
    Method m7 = class_getClassMethod(pCla, @selector(say666));
    Method m8 = class_getClassMethod(metaCla, @selector(say666));
    NSLog(@"m5:%p", m5);
    NSLog(@"m6:%p", m6);
    NSLog(@"m7:%p", m7);
    NSLog(@"m8:%p", m8);
    

    输出结果:


    • m5&m6:sayHi是实例方法,因此在类和元类中查找类方法,是肯定找不到。因此输出null
    • m7&m8:say666是类方法,存储的位置是在元类中。==但是输出结果却是pClametaCla中都能找到,此时需要源码来帮助我们解惑。==
    2.2.2 class_getClassMethod源码分析
    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        
        //注意cls->getMeta()
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    
    ⏬⏬⏬
    
    Class getMeta() {
        if (isMetaClass()) return (Class)this;//元类直接返回
        else return this->ISA();
    }
    
    • 通过源码,看到class_getClassMethod调用的class_getInstanceMethod,这个函数我们上个示例已经研究过,就是找到类中的示例方法。
    • 但是参数有变化,第一个参数是cls->getMeta(),获取类的元类。
    • getMeta实现中,做了一层判断,如果是元类就直接返回;如果不是,获取当前类的元类。

    通过源码的查看,我们了解到示例中m7m8都有值的原因。在调用class_getClassMethod时,分别传入的第一个参数是类和元类。

    • m7:传入的是类,getMetaelse分支,返回元类。在元类中查找say666类方法。
    • m8:传入的是元类,getMeta走if分支,返回自己。同理也可以找到。

    2.3 class_getMethodImplementation

    class_getMethodImplementation作用:查找方法的imp

    2.3.1 示例
    IMP imp1 = class_getMethodImplementation(pCla, @selector(sayHi));
    IMP imp2 = class_getMethodImplementation(metaCla, @selector(sayHi));
    
    IMP imp3 = class_getMethodImplementation(pCla, @selector(say666));
    IMP imp4 = class_getMethodImplementation(metaCla, @selector(say666));
    
    NSLog(@"imp1:%p", imp1);
    NSLog(@"imp2:%p", imp2);
    NSLog(@"imp3:%p", imp3);
    NSLog(@"imp4:%p", imp4);
    

    输出结果:


    image.png
    • imp1&imp4:这个很好理解,实例方法sayHi和类方法say666,分别通过类、元类与sel查找到imp
    • imp2&imp3:在类中查找类方法的imp、元类中查找实例方法的imp。为什么会有值,而且值还相等。同理进入源码看原因。
    2.3.2 class_getMethodImplementation源码分析
    IMP class_getMethodImplementation(Class cls, SEL sel)
    {
        IMP imp;
    
        if (!cls  ||  !sel) return nil;
    
        imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
    
        // Translate forwarding function to C-callable external version
        if (!imp) {
            return _objc_msgForward;
        }
    
        return imp;
    }
    

    通过源码看到,如果找不到imp,就会同意调用_objc_msgForward这个函数,这就是imp能找到并且返回相同地址原因。

    3、isKindOfClass & isMemberOfClass


    这两个方法在正常开发流程中,使用率还是挺高的。但是很多人可能不了解,这两个方法也有类方法(+方法)。先来看看示例:

    3.1 示例

    BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [[DZPerson class] isKindOfClass:[DZPerson class]];
    BOOL re4 = [[DZPerson class] isMemberOfClass:[DZPerson class]];
    NSLog(@"re1 :%d", re1);
    NSLog(@"re2 :%d", re2);
    NSLog(@"re3 :%d", re3);
    NSLog(@"re4 :%d", re4);
    
    NSLog(@"====================分割线====================");
    
    BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [[DZPerson alloc] isKindOfClass:[DZPerson class]];
    BOOL re8 = [[DZPerson alloc] isMemberOfClass:[DZPerson class]];
    NSLog(@"re5 :%d", re5);
    NSLog(@"re6 :%d", re6);
    NSLog(@"re7 :%d", re7);
    NSLog(@"re8 :%d", re8);
    

    注意:上半部分调用的是类方法,下半部分调用实例方法。

    输出结果:

    re1 :1
    re2 :0
    re3 :0
    re4 :0
    ====================分割线====================
    re5 :1
    re6 :1
    re7 :1
    re8 :1
    
    • 如果结果与你想的一样,或者你看到结果能够推断出来原因,说明你对isa和superclass的走位图已经很熟悉了。
    • 如果不理解,请继续往下看。

    3.2 isKindOfClass & isMemberOfClass源码

    先把isa走位图放在这里,请再仔细看看


    3.2.1 objc_opt_isKindOfClass源码

    接着我们看源码:
    此处需要注意,isKindOfClass的imp是objc_opt_isKindOfClass使用汇编调试的时候可以确认这点

    此处和alloc方法一样,在llvm编译的时候,苹果底层进行了hook

    接下来看看objc_opt_isKindOfClass实现,解释一下上面的示例

    // Calls [obj isKindOfClass]
    BOOL
    objc_opt_isKindOfClass(id obj, Class otherClass)
    {
    #if __OBJC2__
        if (slowpath(!obj)) return NO;
        Class cls = obj->getIsa();
        if (fastpath(!cls->hasCustomCore())) {
            for (Class tcls = cls; tcls; tcls = tcls->superclass) {
                if (tcls == otherClass) return YES;
            }
            return NO;
        }
    #endif
        return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
    }
    
    • 实现中有两个参数,objotherClass
      • objisKindOfClass方法的调用者
      • otherClassisKindOfClass的参数
    • 源码中通过获取objisa,通过isa的继承链进行查找比较
      • obj是对象,那么isa就是对象的类
      • obj是类,那么isa就是元类

    了解完实现再来对示例中代码解读一下:

    • BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
      • 这句代码第一次循环,获取NSObject的元类,也就是根元类,与NSObject对比。第一次比较失败。
      • 第二次循环,根元类找到它的父类,也就是NSObject类。再与NSObject类比较,此时相等,所以打印结果是1
    • BOOL re3 = [[DZPerson class] isKindOfClass:[DZPerson class]];
      • DZPerson的元类与DZPerson类进行对比。不相等继续找DZPerson的元类的父类一直到nil,都找不到与DZPerson相等,所以打印结果是0
    • BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];NSObject实例的类就是NSObject,与NSObject比较必然相等,打印1
    • BOOL re7 = [[DZPerson alloc] isKindOfClass:[DZPerson class]];:这个同理,打印1
    3.2.2 isMemberOfClass源码
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    
    • 类方法isMemberOfClass,是用self的isa与传入的参数进行对比

      • BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];NSObject的元类与NSObject不相等,所以打印结果是0
      • BOOL re4 = [[DZPerson class] isMemberOfClass:[DZPerson class]];:同理,DZPerson的元类与DZPerson也不相等,打印结果是0
    • 实例方法isMemberOfClass,获取自己的类与参数比较,re6re8都打印1

    3.3 小结

    • isKindOfClass的实例方法和类方法,它们的的imp都是objc_opt_isKindOfClass函数。
    • isMemberOfClass类方法找的是类的isa,实例方法找的的类。
    • isKindOfClass会循环用父类和参数比较,最后找到nil
    • isMemberOfClass只会比较一次
    • 最重要的点,一定要理解isa和superclass的走位图。

    相关文章

      网友评论

        本文标题:类(二)-- method归属

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