Objective-C
(以下簡稱 OC
)是一門動態(tài)性強(qiáng)的編程語言泊藕,OC
的動態(tài)性是基于 Runtime
來實現(xiàn)的,Runtime
系統(tǒng)是由 C\C++\匯編語言
編寫的虐译,提供的 API
基本都是 C
語言的忿薇。這里我們從蘋果提供的 Runtime
代碼來探究類的本質(zhì)毕骡。
legacy 版本
OC
的 runtime
分為兩個版本.一個是 legacy
版本,一個是 modern
版本。相信很多讀者都見過下面這段代表 OC
類結(jié)構(gòu)的代碼:
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
版本 已經(jīng)在 2006
年的 WWDC
大會上發(fā)布 Objective-C 2.0
后棄用了奏候, OBJC2_UNAVAILABLE
標(biāo)記的內(nèi)容已經(jīng)不再使用循集,那么現(xiàn)在的結(jié)構(gòu)是什么呢?
對象
OC
中蔗草,每一個對象都是類的實例咒彤,先直接來看源碼中的結(jié)構(gòu):
struct objc_object {
private:
isa_t isa;
// ...
}
代表對象的結(jié)構(gòu)中只有一個 isa
的成員變量疆柔,在 arm64
架構(gòu)下,系統(tǒng)對 isa
進(jìn)行了優(yōu)化镶柱,它不光存著地址信息旷档,還存著其他信息。因此對象的本質(zhì)就是包含了一個私有成員變量 isa
的結(jié)構(gòu)體奸例,而 isa
存著的地址就指向著對象所屬的類彬犯。不同的對象有不同的成員變量,編譯后查吊,每個對象的結(jié)構(gòu)體也會存著自己的成員變量的值谐区。
使用命令獲取編譯后的代碼 -sdk iphoneos clang -arch arm64 -rewrite-objc Coder.m
@interface Coder : Person
@property (nonatomic, copy) NSString *name;
@end
// 編譯后查看 `Coder` 的實現(xiàn)
struct NSObject_IMPL {
Class isa;
};
struct Coder_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString * _Nonnull _name;
};
之所以成員變量的值存在對象中,這個也很好理解逻卖,每個對象肯定是獨(dú)立存在的宋列,都需要擁有自己的變量值。而變量名稱和方法等等存在什么地方呢评也,就是類了炼杖!
類
類存著成員變量的類型,方法等等盗迟,源碼如下:
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();
}
// 省略...
}
首先可以看到的一點(diǎn)是 objc_class
繼承了 objc_object
坤邪,因此其實 OC
中的類也可以理解為一種對象,稱之為類對象罚缕,在 legacy
版本中艇纺,對象的結(jié)構(gòu)體中只有一個 isa
指針,指向它的類對象邮弹,而類對象中也有一個 isa
指針黔衡,指向它的元類。modern
版本使用繼承后腌乡,類對象的結(jié)構(gòu)體就繼承了這個優(yōu)化后的 isa
變量盟劫。但對比兩個版本,會發(fā)現(xiàn) modern
版本中除了superclass&cache
与纽,其余的很多變量不在了侣签,并多了一個 bits
變量。
struct class_data_bits_t {
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...
}
這個結(jié)構(gòu)體里面是通過一個位運(yùn)算獲取的指向 class_rw_t
的指針渣锦,可見 bits
存著 class_rw_t
結(jié)構(gòu)體的指針和一些其他信息硝岗。然后把目光轉(zhuǎn)到 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
版本中的方法、屬性和協(xié)議列表就存在這個里面袋毙,這幾個列表可以理解為是二維數(shù)組型檀,是可讀可寫的,包含了類的初始內(nèi)容听盖、分類的內(nèi)容胀溺,二維數(shù)組方便增加裂七。 而這里又有一個 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
可以理解為是一維數(shù)組,是只讀的无埃,包含了類的初始內(nèi)容徙瓶。
從這里我們也能看出分類不能動態(tài)添加成員變量到類對象的原因,分類是通過
runtime
加載的嫉称,這時候類結(jié)構(gòu)已經(jīng)確定下來了侦镇,并且這里保存成員變量的內(nèi)存是只讀的。
元類
上面已經(jīng)提到织阅,類對象的 isa
中儲存的地址指向的就算類對象的類壳繁,稱之為元類,元類儲存著對象方法荔棉。也就是說實例方法是儲存在類中的闹炉,類方法是存儲在元類中的。用一個經(jīng)典的圖來表示對象润樱、類和元類的關(guān)系渣触。
圖中已經(jīng)很好的闡述了三者之間的關(guān)系,不過這里需要強(qiáng)調(diào)兩點(diǎn)壹若。
- 元類的
isa
指向的是基類的元類昵观。 - 基類的元類的
superclass
指向的是基類
這兩個點(diǎn)很容易被忽略,在一些面試題中經(jīng)常出現(xiàn)舌稀。