方法的存储位置和类的类型判断

820 阅读5分钟

一、方法的存储位置

实验代码

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface AClass: NSObject

+ (void)classMethod;
- (void)instanceMethod;

@end

@implementation AClass

+ (void)classMethod {
    
}

- (void)instanceMethod {
    
}

@end

void log_getInstanceMethod(Class cls) {
    const char *className = class_getName(cls);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(cls, @selector(instanceMethod));
    Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
    
    Method method3 = class_getInstanceMethod(cls, @selector(classMethod));
    Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));
    
    NSLog(@"%s-%p-%p-%p-%p", __func__, method1, method2, method3, method4);
}

void log_getClassMethod(Class cls) {
    const char *className = class_getName(cls);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(cls, @selector(instanceMethod));
    Method method2 = class_getClassMethod(metaClass, @selector(instanceMethod));
    
    Method method3 = class_getClassMethod(cls, @selector(classMethod));
    Method method4 = class_getClassMethod(metaClass, @selector(classMethod));
    
    NSLog(@"%s-%p-%p-%p-%p", __func__, method1, method2, method3, method4);
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        Class aClass = [AClass class];
        log_getInstanceMethod(aClass);
        log_getClassMethod(aClass);
    }
    return 0;
}


// 输出
log_getInstanceMethod-0x1000020f8-0x0-0x0-0x100002090

log_getClassMethod-0x0-0x0-0x100002090-0x100002090

实例方法的存储位置

log_getInstanceMethod - 0x1000020f8 - 0x0 - 0x0 - 0x100002090

-instanceMethod 实例方法存储在类对象 cls 中
-classMethod 类方法(类对象的实例方法)存储在元类对象 metaClass 中。

class_getInstanceMethod 源码

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
#warning fixme build and search caches
    // Search method lists, try method resolver, etc.
    // 1.初始化类对象,以及类对象的方法索引
    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();
    // fixme nil cls?
    // fixme nil sel?
    ASSERT(cls->isRealized());

    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        // 3.当前类查不到,就查父类,父类也没找到就停止
        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?

    // 2.遍历匹配当前 cls 对象的 bits 中的 data()存储的方法
    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;
}

类方法的存储位置

log_getClassMethod - 0x0 - 0x0 - 0x100002090 - 0x100002090

  • instanceMethod 实例方法用 class_getClassMethod 获取不到
  • classMethod 从类对象 cls 和 元类对象 metaClass 都可以拿到,这是为什么呢?

上源码

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

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

Class getMeta() {
    // 如果当前是元类,就返回自己
    if (isMetaClass()) return (Class)this;
    // 如果当前不是元类,就返回当前类对象的 isa
    else return this->ISA();
}

从源码可知,cls 获取类方法时,其实是取 cls->isa 的存储实例方法,而 cls->isa 就是元类对象 metaClass。而使用 metaClass 获取类方法时,由于是元类,因此返回了他自己,获取它自己存储的实例方法。

二、类的判断逻辑

实验代码

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface AClass: NSObject

@end

@implementation AClass

@end

void log_classKindInfo() {
    BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [[AClass class] isKindOfClass:[AClass class]];
    BOOL re4 = [[AClass class] isMemberOfClass:[AClass class]];
    NSLog(@"\n re1: %hhd\n re2: %hhd\n re3: %hhd\n re4: %hhd\n", re1, re2, re3, re4);
    
    BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [[AClass alloc] isKindOfClass:[AClass class]];
    BOOL re8 = [[AClass alloc] isMemberOfClass:[AClass class]];
    NSLog(@"\n re5: %hhd\n re6: %hhd\n re7: %hhd\n re8: %hhd\n", re5, re6, re7, re8);
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        log_classKindInfo();
    }
    return 0;
}

// 输出信息
 re1: 1
 re2: 0
 re3: 0
 re4: 0
 re5: 1
 re6: 1
 re7: 1
 re8: 1

分析 isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    // 类对象查询,判断当前类对象的 isa 和 传进来的 cls 是否一致
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    // 实例对象查询,判断当前实例的 isa 和 传进来的 cls 是否一致
    return [self class] == cls;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    // 获取当前实例的 isa
    if (obj) return obj->getIsa();
    else return Nil;
}

如下表:

对象含义isa对比isa对比结果
[NSObject class]NSObject 的类对象NSObject 元类对象[NSObject class]re2 = false
[AClass class]AClass 的类对象AClass 元类对象[AClass class]re4 = false
[NSObject alloc]NSObject 的实例对象NSObject 类对象[NSObject class]re6 = true
[AClass alloc]AClass 的实例对象AClass 类对象[AClass class]re6 = true

分析 isKindOfClass

源码

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    // 获取当前对象的 isa
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        // 如果当前 isa 与目标类对象不同,则上溯父类查找
        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);
}

如下表: isKindOfClass

对象含义isa对比isa对比结果
[NSObject class]NSObject 的类对象NSObject 元类对象[NSObject class]re1 = true
[AClass class]AClass 的类对象AClass 元类对象[AClass class]re3 = false
[NSObject alloc]NSObject 的实例对象NSObject 类对象[NSObject class]re5 = true
[AClass alloc]AClass 的实例对象AClass 类对象[AClass class]re7 = true

问题出现了 re1 为什么是 true? 明明 [NSObject class] 的 isa 是 NSObject 的元类对象,为什么 [NSObject class] 相等?

其实并不是这两个相等,而是在 NSObject 根元类的 superClass 是 NSObject 类,因此这里 re1 为true。

注意
isMemberOfClass 只会查询当前对象的 isa,不会上溯父类
isKindOfClass 当前 isa 不匹配,会上溯父类查找

三、NSObject +(BOOL)isKindOfClass: 什么时候调用

在第二部分分析中,我们发现,NSObject 中实现的 +isKindOfClass-isKindOfClass 并没有调用,而是优化为了 objc_opt_isKindOfClass 函数调用。那么有没有办法能调用到呢?

如下代码,可以调用到 +isKindOfClass 方法,但是只调用了一次,第二次调用没有执行 NSObject 定义到 +isKindOfClass 方法

[AClass isKindOfClass:[NSObject class]];
[AClass isKindOfClass:[NSObject class]];

为什么呢?我们断点调试以下发现:

即 cls->hasCustomCore() == true 而第二次 cls->hasCustomCore() == false

上源码追踪

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

bool hasCustomCore() const {
    // 此处取反
    return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
}

void setHasDefaultCore() {
    return cache.setBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
void setHasCustomCore() {
    return cache.clearBit(FAST_CACHE_HAS_DEFAULT_CORE);
}

bool getBit(uint16_t flags) const {
    return _flags & flags;
}

由此我们可以判断,第一次的时候 _flags 不包含 FAST_CACHE_HAS_DEFAULT_CORE;第二次调用的时候,_flags 已经包含了 FAST_CACHE_HAS_DEFAULT_CORE。

我们在 setHasDefaultCore 和 setHasCustomCore 打上断点,看什么时候赋值了。

由此可知,在调用 objs_msgSend 的时候, 调用到了 lookUpImpOrForward 函数。

而且在 lookUpImpOrForward 函数,触发了 initializeAndLeaveLocked 函数,原因是 !cls->isInitialized() !!!

破案了,因为当前的 AClass 类对象未初始化,所以 _flags 为空,进而导致 cls->hasCustomCore() == true。

而类初始化之后,就不满足此条件了。

注意 [AClass class] 和 [AClass alloc] 都会触发 AClass 对象的初始化方法。