iOS类原理经典面试题分析

225 阅读5分钟

前言

本章来分析两例经典的类原理面试题:通过对如下的两对接口来加深下对类的一些底层原理的理解。

  1. isKindOfClass/isMemeberOfClass
  2. class_getInstanceMethod/class_getClassMethod

一、前置知识

isa和superclass的指向图

关于实例对象、类对象和元类对象之间的isa和superclass的指向,有一张非常讲点的图片可以清晰的说明(ps还是偷图来的爽啊):

从横向分析isa的走向:

实例对象(class instance)->类对象(class)->元类对象(metaClass)->根元类对象(rootMetaClass)->根元类对象(rootMetaClass,isa从此处进入无限制循环了)

从竖向分析superclass走向:

1. 只有class对象和metaClass对象有superclass,实例对象instance是没有的
2. class对象和metaClass的继承关系是对应的:即A(class)->B(class),A(metaClass)->B(metaClass)
3. superclass走向从根类处合流:NSObject(metaClass)->NSObject(class)
4. NSObject(class)->nil,到此superclass的溯源停止。

类方法和实例方法的存储

方法的存储要从从运行时源码objc_class的分析

  1. 在类LGPerson中定义类方法和实例方法:
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
    }
    return 0;
}
  1. 分析objc_class:class_data_bits_t 地址偏移32节
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }

实例方法存在类对象中

按照如下LLDB指定执行:从类对象结构体找到实例方法

(lldb) p/x person.class
(Class) $0 = 0x0000000100002148 LGPerson
// 偏移32字节
(lldb) p (class_data_bits_t*)0x0000000100002168
(class_data_bits_t *) $1 = 0x0000000100002168
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001006f6070
(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020e8
      arrayAndFlag = 4294975720
    }
  }
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x00000001000020e8
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHello"
      types = 0x0000000100000fa4 "v16@0:8"
      imp = 0x0000000100000e90 (KCObjc`-[LGPerson sayHello])
    }
  }
}

类方法存在元类对象中

按照如下LLDB指定执行:从元类metaClass对象找到类方法

(lldb) x/4gx person.class
0x100002148: 0x0000000100002120 0x00000001003f1140
0x100002158: 0x0000000101a065f0 0x0002801000000003
// isa找到元类对象
(lldb) po 0x0000000100002120 & 0x0ffffffff8UL
LGPerson

(lldb) p/x 0x0000000100002120 & 0x0ffffffff8UL
(unsigned long) $12 = 0x0000000100002120
// 偏移32字节
(lldb) p/x 0x0000000100002140
(long) $13 = 0x0000000100002140
(lldb) p (class_data_bits_t*) $13
(class_data_bits_t *) $14 = 0x0000000100002140
(lldb) p $14->data()
(class_rw_t *) $15 = 0x00000001006f6050
(lldb) p $15->methods()
(const method_array_t) $16 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002080
      arrayAndFlag = 4294975616
    }
  }
}
(lldb) p *($16.list)
(method_list_t) $17 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100000fa4 "v16@0:8"
      imp = 0x0000000100000e80 (KCObjc`+[LGPerson sayHappy])
    }
  }
}
(lldb) p $17.get(0)
(method_t) $18 = {
  name = "sayHappy"
  types = 0x0000000100000fa4 "v16@0:8"
  imp = 0x0000000100000e80 (KCObjc`+[LGPerson sayHappy])
}

二、 面试题1:isKindOfClass/isMemeberOfClass

问题

@interface LGPerson : NSObject
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        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]];
        BOOL re5 = [(id)[LGPerson class] isKindOfClass:[NSObject class]];
        BOOL re6 = [(id)[LGPerson class] isMemberOfClass:[NSObject class]];
        NSLog(@"%hhd->%hhd->%hhd->%hhd->%hhd->%hhd",re1,re2,re3,re4,re5,re6);

        BOOL re7 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];     
        BOOL re8 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re9 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];     
        BOOL re10 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; 
        BOOL re11 = [(id)[LGPerson alloc] isKindOfClass:[NSObject class]];
        BOOL re12 = [(id)[LGPerson alloc] isMemberOfClass:[NSObject class]];
        NSLog(@"%hhd->%hhd->%hhd->%hhd->%hhd->%hhd",re7,re8,re9,re10,re11,re12);
    }
    return 0;
}

分析可知涉及到2对个方法:(+/-)isKindOfClass:(+/-)isMemberOfClass:,每对都有一个类方法和实例方法,要搞清楚区别,肯定是进行源码分析。

(+/-)isKindOfClass:源码分析

  1. +isKindOfClass:从元类metaClass依据superclass向上追溯,查找是否有指定的参数对象
// 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);
}

调试可知,(+/-)isKindOfClass:会进入objc_opt_isKindOfClass(LLVM有优化),从此处可知isKindOfClass:无论是类方法还是实例方法,本质是一样的.

关键代码就是一个循环:

  1. 起始:isa指向, 实例对象指向类,类对象的isa指向元类
  2. 迭代:tcls = tcls->superclass,依据superclass指向
  3. 终止: 要么找到,要么走到nil。
    re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];为例分析,如下图:

结论:(+/-)isKindOfClass:本质是相同的:从self的isa开始,依据superclass的指向逐一对比

(+/-)isMemberOfClass:源码分析

  1. +isMemberOfClass: 直接比较元类
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
  1. -isMemberOfClass: 直接比较类
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

结论:(+/-)isMemberOfClass本质相同:比较self的isa,注意没有superclass任何关系

isMemberOfClass和isKindOfClass的区别就是是否涉及superclass的追溯

上面面试题的输出如下(你做对了吗?)

1->0->0->0->1->0
1->1->1->1->1->0

面试题2:class_getInstanceMethod()/class_getClassMethod()

背景:LGPerson类声明并实现了实例方法sayHello,类方法sayHappy

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

@end
  1. class_getInstanceMethod()
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);
}

class_getInstanceMethod查找实例方法,实例方法是存在类对象中的,所以:

sayHello实例方法存在类对象中,不是元类中:method1存在,method2不存在 sayHappy类方法存在元类中,所以method3不存在,method4存在

  1. class_getClassMethod()
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));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

看下class_getClassMethod()源码:

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

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

可以看出class_getClassMethod调用了class_getInstanceMethos(),区别在于传入的是元类

// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

so,分析面试题:

sayHello是实例方法,不在元类中,method1和method2不存在 sayHappy是类方法,在元类中,method3和method4存在