首先来看一道经典面试题:
以下代码的打印结果是什么?
BOOL rs1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL rs2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL rs3 = [[Person class] isKindOfClass:[Person class]];
BOOL rs4 = [[Person class] isMemberOfClass:[Person class]];
NSLog(@"rs1 = %d,rs2 = %d,rs3 = %d,rs4 = %d",rs1,rs2,rs3,rs4);
正确的结果应该是:rs1 = 1,rs2 = 0,rs3 = 0,rs4 = 0
为什么结果会是这个样子呢?这个地方我们不得不先插入几个概念:
isa、元类
OC的底层80%是C语言,平常创建的对象(类也属于对象,类对象)都转换成了底层C语言里面的结构体。
打开objc.h文件可以看到如下代码:
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
从定义来看,id是一个指向结构体objc_object的指针,同样在本文件中可以查看到结构体objc_object的定义:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
结构体objc_object内部定一个指针isa,这个isa指向了Class,Class又是什么?同样在objc.h里面找到了这个Class的定义:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
这个Class是一个指向结构体objc_class的一个指针,而这个objc_class的定义在runtime.h里面做了详细的介绍:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
objc_class里面定义了关于这个类的一些信息,比如varsion(版本)、objc_ivar_list(变量列表)、objc_method_list(方法列表)等。值得注意的的是objc_class内部同objc_objcec一样也包含了一个isa,而这个isa是则是指向类对象的元类(meta)的。
关系如图:
注:虚线即表示isa指针指向。
第一列表示的是初始化的对象,也就是刚刚提到过的objc_object,这个对象里面有一个isa指针指向它所属的类,也就是图中的第二列;objc_class结构体里面存放的isa指针即指向了它的元类,也就是第三列,第三列的所有元类都指向了上帝类NSObject的元类(meta)。从图上可以看出,上帝类NSObject的元类最后又指向了NSObject这个类本身,所以构成了一个回路。
简单的了解了对象、类、元类的关联以后,来看一下一开始提到的那个面试题。
[NSObject class] 这是一个类的调用方法,calss内部是这样实现的:
+ (Class)class {
return self;
}
很明显,返回的是当前的调用者本身,也就是NSObject这个类。
再来看一下isKindOfClass和isMemberOf的内部实现:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
从上面的代码中可以看的出来,一个循环语句,cls类指向的是isa,简单的说是在循环找父类。
BOOL rs1 = [[NSObject class] isKindOfClass:[NSObject class]];
第一次比较:拿着方法左边 [NSObject class](即NSObject本身)tcls,调用了object_getClass方法
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
object_getClass方法返回的是对象的isa指针,NSObject类的isa指针指向NSObject的元类,方法右边[NSObject class](即NSObject本身)cls做比较。而左边[NSObject class](即NSObject本身)的isa指向的是当前类对应的元类(meta class)。很明显,左边的meta class和右边的NSObject不想等,所以返回的是NO。
第二次比较:由上图可知,NSObject的元类(meta class)的父类是NSObject,所以这一次循环取meta class 的父类(即NSObjce),左边NSObjec = 右边NSObjec,所以 rs1 = 1.
同理剩余的三个可以验证。
isKindOfClass和ismemberofclass的区别:
isKindOfClass是进行循环地查找调用者及其父类是否和后者属于同一类;
ismemberofclass仅仅比较调用者的isa指向类是否和后者属于同一类。
如果题目中的
BOOL rs3 = [[Person class] isKindOfClass:[Person class]];
变成
BOOL rs3 = [[Person New] isKindOfClass:[Person class]];
返回的结果就是真了。
顺便补充一句:
object_getClass:获得的是isa的指向
self.class:当self是实例对象的时候,返回的是类对象,否则返回自身
值得注意的是:类方法和实例方法有很大的不同,这个和类对象和实例对象的本质有关,所以查找的方式也会有不同。
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}