方法归属及isKindOfClass、isMemberOfCla

2020-09-16  本文已影响0人  iOSer_jia

本文将会对两个题目进行探究。

方法归属问题

开始先来看一个题目。

void lgInstanceMethod(Class pClass) {
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(eat));
    Method method2 = class_getInstanceMethod(metaClass, @selector(eat));
    
    Method method3 = class_getInstanceMethod(pClass, @selector(run));
    Method method4 = class_getInstanceMethod(metaClass, @selector(run));
    
    NSLog(@"instance method: %p - %p - %p - %p", method1, method2, method3, method4);
}

void lgClassMethod(Class pClass) {
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(eat));
    Method method2 = class_getClassMethod(metaClass, @selector(eat));
    
    Method method3 = class_getClassMethod(pClass, @selector(run));
    Method method4 = class_getClassMethod(metaClass, @selector(run));
    
    NSLog(@"class Method: %p - %p - %p - %p", method1, method2, method3, method4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class pClass = Animal.class;
        
        lgInstanceMethod(pClass);
        
        lgClassMethod(pClass);
    }
    return 0;
}

Animal的定义如下:

@interface Animal : NSObject

@end

@implementation Animal

- (void)eat {
    NSLog(@"eat~~~~");
}

+ (void)run {
    NSLog(@"run~~~~");
}

@end

问instance method和class method分别会打印什么?

要解决这个问题,我们需要看class_getInstanceMethodclass_getClassMethod的定义。

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

    Method meth;
    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
    if (meth == (Method)1) {
        // Cache contains forward:: . Stop searching.
        return nil;
    } else if (meth) {
        return meth;
    }
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
    if (meth == (Method)1) {
        // Cache contains forward:: . Stop searching.
        return nil;
    } else if (meth) {
        return meth;
    }

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

    // fixme nil cls?
    // fixme nil sel?

    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());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

最后找到关键代码

auto const methods = cls->data()->methods();

而在上一篇文章中我们知道,类对象的methods中存放的是实例方法,元类的methods存放的是类方法,所以

instance method打印应该是method1,method4有值,method2,method3为nil。

接着看class_getClassMethod的定义

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

可以看到它的内部实现也是调用了class_getInstanceMethod,不同是它的传入参数是cls->getMeta(),查看它的定义,

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

显然,如果传入的当前类是类对象,那么就返回类isa指向的元类对象,如果当前类是元类,那么返回自己本身。所以根据类方法存放在元类中,所以

class method打印应该是method1,method2为nil,method3,method4有值。

验证结果

2020-09-16 12:21:42.774155+0800 isa_test[93360:4177590] instance method: 0x100002170 - 0x0 - 0x0 - 0x100002108
2020-09-16 12:21:42.774206+0800 isa_test[93360:4177590] class Method: 0x0 - 0x0 - 0x100002108 - 0x100002108

isKindOfClass和isMemberOfClass

接下来是这个题目

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     
BOOL re3 = [(id)[Animal class] isKindOfClass:[Animal class]];       
BOOL re4 = [(id)[Animal class] isMemberOfClass:[Animal class]];     
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
BOOL re7 = [(id)[Animal alloc] isKindOfClass:[Animal class]];       
BOOL re8 = [(id)[Animal alloc] isMemberOfClass:[Animal class]];     
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

同样我们在源码objc4-7.8.1中查找答案。

首先是类方法的isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

可以看到类方法isKindOfClass会先通过isa指针找到根元类,先比较元类和cls,而后在依次通过superclass找父元类遍历比较,而通过isa走位图(详见类结构探究(一)-- isa与superclass的指向),可以知道元类的superclass最终会走到根元类,而根元类的superclass指向了根类NSObject,所以,re1的结果为YES,re3结果为NO。

再来看类方法的isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

它是直接通过类的isa指向与目标class进行比较,当isa指向的是元类,元类和类并不相等,所以re2和re4为NO。

在来看实例方法的isKindOfClass

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

实例方法的isKindOfClass会先找到类进行比较,无法匹配在通过superclass遍历父类进行匹配知道根类NSObject,所以re5和re7都为YES,

而实例方法的isMemberOfClass

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

只是简单当前类比较,说re6和re7也为YES。

根据探索,结论为

验证结果

2020-09-16 14:52:08.522482+0800 isa_test[96542:4235809]  re1 :1
 re2 :0
 re3 :0
 re4 :0
2020-09-16 14:52:08.522546+0800 isa_test[96542:4235809]  re5 :1
 re6 :1
 re7 :1
 re8 :1

思考

这两道看似简单,但实际上是对类结构已经isa走位的一个考察,另外,我们无法通过方法名臆断方法执行结果,所以学习最好的途径还是多阅读源码,多思考。

上一篇 下一篇

猜你喜欢

热点阅读