首先來看一道經典面試題:
以下代碼的打印結果是什麼?
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)isKindOf:aClass
{
Class cls;
for (cls = isa; cls; cls = cls->superclass)
if (cls == (Class)aClass)
return YES;
return NO;
}
- (BOOL)isMemberOf:aClass
{
return isa == (Class)aClass;
}
從上面的代碼中可以看的出來,一個循環語句,cls類指向的是isa,簡單的說是在循環找父類。
BOOL rs1 = [[NSObject class] isKindOfClass:[NSObject class]];
第一次比較:拿着方法左邊 [NSObject class](即NSObject本身)的isa指向的類去和方法右邊[NSObject class](即NSObject本身)做比較。而左邊[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是實例對象的時候,返回的是類對象,否則返回自身