关于类结构的面试题分析

1. class_getInstanceMethod 与 class_getClassMethod 分析

首先定义两个类和方法

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation LGPerson

- (void)sayHello{
}

+ (void)sayHappy{
}

@end


@interface LGTeacher : NSObject
- (void)say666;
@end
@implementation LGTeacher
- (void)say666{
    
}
@end

测验代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);

        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
    }
    return 0;
}
/// 获取 InstanceMethod 方法
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

/// 获取 classMethod 方法
void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

第一组打印结果 lgInstanceMethod_classToMetaclass - 0x1000081f8-0x0-0x0-0x100008190 结果显示method1方法(LGPerson类的实例方法)和method4LGPerson元类类方法)方法被找到。来,分析一波!

class_getInstanceMethod方法实现

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.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

_class_getMethod方法源码实现

static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}

getMethod_nolock方法源码实现

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

while循环中关键指向cls = cls->superclass,条件为 cls存在并且method_t m没有找到,再看下getMethodNoSuper_nolock方法是怎么找的

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    // class_rw_t ->methods()
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //  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;
}

其实就是通过cls -> data() -> methods()找到方法列表循环遍历寻找。跟上篇 ISA指向、类结构通过lldb分析方法存储位置相似,我们验证的结果是类的实例方法储存在该类中methods()方法中,类方法储存在该类的元类中class_getInstanceMethod方法流程就是通过 -> 父类... -> 根类 的循环从data() -> methods()中循环遍历寻找,所以method2method4是寻找不到的。

第二组打印结果:lgClassMethod_classToMetaclass-0x0-0x0-0x100008190-0x100008190结果显示method3方法(LGPerson类的类方法)和method4(LGPerson元类的类方法)方法被找到。来,看源码!

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

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

class_getInstanceMethod 它又出现了,这次省事儿了,直接看 getMeta方法。

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

class_getClassMethod 其实就是把当前传入类做了判断,如果传入的是元类,走class_getInstanceMethod遍历循环,传入的不是元类,返回元类走class_getInstanceMethod遍历循环。

2. isKindOfClass 与 isMemberOfClass

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson 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)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

第一组打印数据

re1 :1
re2 :0
re3 :0
re4 :0

开始分析

+ (BOOL)isKindOfClass:(Class)cls {
    // 查找顺序 cls元类 ->父类 ->.... ->NSObject元类 ->NSObject
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
    // 元类和传入类做对比
    return self->ISA() == cls;
}

第二组打印数据

re5 :1
re6 :1
re7 :1
re8 :1

再次分析

- (BOOL)isKindOfClass:(Class)cls {
    // 查找顺序  当前对象的类 ->父类 ->... ->NSObject
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isMemberOfClass:(Class)cls {
    // 对象的 class方法与传入类做对比,相当于类的实例对象的ISA()。
    return [self class] == cls;
}

总结

isKindOfClass 方法与isMemberOfClass不管类方法还是实例方法,都是对当前调用者的 isa指针做对比,只是前者遍历后者不遍历的区别。

你可能感兴趣的:(关于类结构的面试题分析)