前言
本章来分析两例经典的类原理面试题:通过对如下的两对接口来加深下对类的一些底层原理的理解。
isKindOfClass/isMemeberOfClass;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的分析
- 在类LGPerson中定义类方法和实例方法:
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
}
return 0;
}
- 分析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:源码分析
- +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:无论是类方法还是实例方法,本质是一样的.
关键代码就是一个循环:
- 起始:isa指向, 实例对象指向类,类对象的isa指向元类
- 迭代:tcls = tcls->superclass,依据superclass指向
- 终止: 要么找到,要么走到nil。
以re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];为例分析,如下图:
结论:(+/-)isKindOfClass:本质是相同的:从self的isa开始,依据superclass的指向逐一对比
(+/-)isMemberOfClass:源码分析
- +isMemberOfClass: 直接比较元类
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- -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
- 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存在
- 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存在