isKindOfClass 和 isMemberOfClass 區別深究

首先來看一道經典面試題:

以下代碼的打印結果是什麼?

 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.

同理剩餘的三個可以驗證。
 

isKindOfClassismemberofclass的區別:

isKindOfClass是進行循環地查找調用者及其父類是否和後者屬於同一類;

ismemberofclass僅僅比較調用者的isa指向類是否和後者屬於同一類。

如果題目中的

 BOOL rs3 = [[Person class] isKindOfClass:[Person class]];

變成

 BOOL rs3 = [[Person New] isKindOfClass:[Person class]];

返回的結果就是了。

順便補充一句:

object_getClass:獲得的是isa的指向
self.class:當self是實例對象的時候,返回的是類對象,否則返回自身

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章