类(二)-- method归属

类(一)-- 底层探索
类(二)-- 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归属)