Objective-C 類的本質

Objective-C (如下簡稱 OC )是一門動態性強的編程語言,OC 的動態性是基於 Runtime 來實現的,Runtime 系統是由 C\C++\彙編語言 編寫的,提供的 API 基本都是 C 語言的。這裏咱們從蘋果提供的 Runtime 代碼來探究類的本質。c++

runtime 源碼地址面試

legacy 版本

OCruntime 分爲兩個版本.一個是 legacy 版本,一個是 modern 版本。相信不少讀者都見過下面這段表明 OC 類結構的代碼:objective-c

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;
複製代碼

其實這段代碼就是 legacy 版本 已經在 2006 年的 WWDC 大會上發佈 Objective-C 2.0 後棄用了, OBJC2_UNAVAILABLE 標記的內容已經再也不使用,那麼如今的結構是什麼呢?編程

對象

OC 中,每個對象都是類的實例,先直接來看源碼中的結構:數組

struct objc_object {
private:
    isa_t isa;
    // ...
}
複製代碼

表明對象的結構中只有一個 isa 的成員變量,在 arm64 架構下,系統對 isa 進行了優化,它不光存着地址信息,還存着其餘信息。所以對象的本質就是包含了一個私有成員變量 isa 的結構體,而 isa 存着的地址就指向着對象所屬的類。不一樣的對象有不一樣的成員變量,編譯後,每一個對象的結構體也會存着本身的成員變量的架構

使用命令獲取編譯後的代碼 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Coder.mapp

@interface Coder : Person
@property (nonatomic, copy) NSString *name;
@end
    
// 編譯後查看 `Coder` 的實現
struct NSObject_IMPL {
	Class isa;
};

struct Coder_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};
複製代碼

之因此成員變量的值存在對象中,這個也很好理解,每一個對象確定是獨立存在的,都須要擁有本身的變量值。而變量名稱和方法等等存在什麼地方呢,就是類了!iphone

類存着成員變量的類型,方法等等,源碼以下:編程語言

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() { 
        return bits.data();
    }
    // 省略...
}
複製代碼

首先能夠看到的一點是 objc_class 繼承了 objc_object,所以其實 OC 中的類也能夠理解爲一種對象,稱之爲類對象,在 legacy 版本中,對象的結構體中只有一個 isa 指針,指向它的類對象,而類對象中也有一個 isa 指針,指向它的元類。modern 版本使用繼承後,類對象的結構體就繼承了這個優化後的 isa 變量。但對比兩個版本,會發現 modern 版本中除了superclass&cache ,其他的不少變量不在了,並多了一個 bits 變量。優化

struct class_data_bits_t {
    uintptr_t bits;
    
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
	// ...
}
複製代碼

這個結構體裏面是經過一個位運算獲取的指向 class_rw_t 的指針,可見 bits 存着 class_rw_t 結構體的指針和一些其餘信息。而後把目光轉到 class_rw_t 上:

'rw' 和 ro' 分別表示 'readwrite' 和 'readonly'

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    // ...
}
複製代碼

能夠看到原先 legacy 版本中的方法、屬性和協議列表就存在這個裏面,這幾個列表能夠理解爲是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容,二維數組方便增長。 而這裏又有一個 class_ro_t :

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    // ....
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    // ....
};
複製代碼

class_ro_t 裏面的 baseMethodList、baseProtocols、ivars、baseProperties 能夠理解爲是一維數組,是隻讀的,包含了類的初始內容。

從這裏咱們也能看出分類不能動態添加成員變量到類對象的緣由,分類是經過 runtime 加載的,這時候類結構已經肯定下來了,而且這裏保存成員變量的內存是隻讀的。

元類

上面已經提到,類對象的 isa 中儲存的地址指向的就算類對象的類,稱之爲元類,元類儲存着對象方法。也就是說實例方法是儲存在類中的,類方法是存儲在元類中的。用一個經典的圖來表示對象、類和元類的關係。

圖中已經很好的闡述了三者之間的關係,不過這裏須要強調兩點。

  • 元類的 isa 指向的是基類的元類。
  • 基類的元類的 superclass 指向的是基類

這兩個點很容易被忽略,在一些面試題中常常出現。

參考

Objective-C 對象模型

相關文章
相關標籤/搜索