Class、isa、元類
本文的所涉及到的原始碼是 objc4 原始碼,截止到寫本文最新的是 objc4-750
這個版本。
Class
我們在學習面向物件的學習中,接觸最多的就是類,那麼在OC類是由Class型別來表示的,Class是用C的資料結構來表示的。
看一下 NSObject
的宣告,在標頭檔案中,如下圖所示:

@interface NSObject<NSObject>{ #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wobjc-interface-ivars" Class isaOBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop }
可以看到:
1、 NSObject
是實現了 <NSObject>
協議的。
2、 NSObject
中有 Class
型別的 isa
成員變數,外界是無法訪問的,另外 isa
指標可能在將來也會被隱藏起來(OBJC_ISA_AVAILABILITY標示了)。
繼續看一下 Class
到底是什麼?
在上面的檔案中可以看到 Class
的定義,如下程式碼:
typedef struct objc_class *Class; typedef struct objc_object *id;
可以看出 Class
是一個指向 objc_class
的結構體指標, Objective-C
中的類是由 Class
型別來表示的,它實際上是一個指向 objc_class
結構體的指標。
在下面的標頭檔案中看一下 objc_class
的定義,如下:
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 // .... }
可以看出, objc_class
用來描述OC中的類,而 objc_object
用來描述OC中的物件,類(objc_class)其實也是一個物件(objc_object),另外 id
是代表物件的,它是指向 objc_object
的結構體指標,它的存在可以讓我們實現類似於C++中泛型的一些操作。該型別的物件可以轉換為任何一種物件,有點類似於C語言中 void *
指標型別的作用。
這裡要注意, objc_class
的定義在 objc-runtime-old.h
中和 objc-runtime-new.h
中的不一樣。這裡以 objc-runtime-new.h
為主,建議可以看看被誤解的 objc_class 這篇文章。
再來看一下 objc_object
,如下圖所示:
struct objc_object { private: isa_t isa; // ... }
objc_object
是一個結構體,裡面有個私有成員變數 isa
是 isa_t
型別的。
而 isa_t
是一個 union 型別的,如下程式碼:
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD;// defined in isa.h }; #endif };
總之在OC中,類也是一個物件稱之為類物件,根據凡是物件都有自己的類的原理,那麼類物件的肯定存在自己的類,這個類就是元類(meta class)。
元類
在說元類之前,先看一下下面的例子,建立一個 NSMutableDictionary
例項物件 dict
,即向 NSMutableDictionary
傳送 alloc
和 init
訊息。
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; ``` 上面程式碼的大概執行流程如下幾個步驟: 1、先執行 `[NSMutableDictionary alloc]`,但是 `NSMutableDictionary` 沒有 `+alloc` 方法,於是再去父類`NSObject` 中查詢該方法。 2、`NSObject` 響應 `+alloc` 方法,開始檢測 `NSMutableDictionary` 類,並根據其所需的記憶體空間大小開始分配記憶體空間,然後把 `isa` 指標指向 `NSMutableDictionary` 類。同時,`+alloc` 也被加進 cache 列表裡面。 3、接著,執行 `-init` 方法,如果 `NSMutableDictionary` 響應該方法,則直接將其加入 `cache`,如果不響應,則去父類查詢。 4、在後期的操作中,如果再以 `[[NSMutableDictionary alloc] init]` 這種方式來建立字典物件,則會直接從 cache 中取出相應的方法,直接呼叫。 上面是建立一個例項物件的大致流程,接下來我們說說元類。 元類簡單來說就是類物件的類。類描述的是物件,那麼元類描述的就是Class類物件的類。元類定義了類的行為(類方法),在平時開發時,meta class 基本是用不著接觸的,但是我們還是要知道它的存在。 ```objc NSMutableDictionary *tDatas = [NSMutableDictionary dictionaryWithCapacity:5];
拿上面的示例來說,向 NSMutableDictionary
傳送 dictionaryWithCapacity
這個訊息的時候,Runtime 會在這個類的 meta-class 的方法列表中查詢,通過 SEL 找到後取出方法中的 IMP 函式入口指標,並執行該方法,如果找不到就進行訊息轉發的流程中,最終可能會導致 Crash,訊息轉發的原理和機制可以參考訊息機制 這幾篇文章。
元類儲存了類方法的列表。當一個類方法被呼叫時,元類會首先查詢它本身是否有該類方法的實現,如果沒有則該元類會向它的父類查詢該方法,直到一直找到繼承鏈的頭。
Class object_getClass(id obj);
object_getClass
可以獲取一個物件的 class object,其原始碼實現如下:
Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
舉個例子吧,示例如下:
NSObject *obj = [NSObject new]; Class obj1 = object_getClass(obj); Class obj2 = object_getClass([NSObject class]); Class obj3 = objc_getMetaClass("NSObject"); Class obj4 = object_getClass(obj1); const char *name = [NSStringFromClass(obj1) UTF8String]; NSLog(@"name: %s", name); //name: NSObject Class obj5 = objc_getMetaClass(name); Class obj6 = objc_getClass(name); NSLog(@"obj : %@, ->%p: ", obj,obj); NSLog(@"obj1: %@, ->%p: ", obj1, obj1); NSLog(@"obj2: %@, ->%p: ", obj2, obj2); NSLog(@"obj3: %@, ->%p: ", obj3, obj3); NSLog(@"obj4: %@, ->%p: ", obj4, obj4); NSLog(@"obj5: %@, ->%p: ", obj5, obj5); NSLog(@"obj6: %@, ->%p: ", obj6, obj6);
列印結果如下:
obj : <NSObject: 0x600002b19d70>, ->0x600002b19d70: obj1: NSObject, ->0x10c96bf38: obj2: NSObject, ->0x10c96bee8: obj3: NSObject, ->0x10c96bee8: obj4: NSObject, ->0x10c96bee8: obj5: NSObject, ->0x10c96bee8: obj6: NSObject, ->0x10c96bf38:
可以看出,obj 是一個例項物件,obj1和obj6是一個 class object,其二者地址也一致,obj2、obj3 和 obj4 都獲取到的是元類。
通過類物件呼叫的 object_getClass
得到的是該類物件的 meta class,如 obj2 和 obj4,而通過例項物件呼叫的 object_getClass
得到的是該例項物件的類物件,如 obj1, objc_getClass
這個方法獲取是例項物件的類物件,與 object_getClass
還是有點不一樣的。而 objc_getMetaClass
可以直接獲取 meta class,如 obj3。
在 NSObject.mm 中,可以看到 self 和 class 方法都要例項和類方法,class 方法返回的都是類物件。
+ (id)self { return (id)self; } - (id)self { return self; } + (Class)class { return self; } - (Class)class { return object_getClass(self); }
所以,無論是類還是例項呼叫 class 方法,返回的都是同一個 class object,舉例:
Class objClz1 = [NSObject class]; Class objClz2 = [[[NSObject alloc] init] class]; if (objClz1 == objClz2) { NSLog(@"objClz1: %@, ->%p", objClz1, objClz1); }
輸出結果是:
objClz1: NSObject, ->0x10fa30f38
isa
下面的例子來源自 這裡 ,感謝 kingizz’s blog,下面的程式碼 Son
是 Father
的子類,而 Father
是 NSObject
的子類。
@interface Father:NSObject @end
@interface Son:Father @end
我們結合下面這個圖來理解一下,子類、父類、元類以及 isa 指標。
一個例項物件的 isa
指向物件所屬的類,這個類的 isa
指向這個類的元類,而這個元類的 isa
又指向 NSObject
的元類, NSObject
的元類的 isa
指向其本身,最終形成形成一個完美的閉環。
在OC中,所有的物件都有一個 isa
指標,指向物件所屬的類,類也是一個物件,類物件的 isa
指標指向類的元類。