Demo: https://github.com/iOSlixiang/RuntimeTest.git
objc4-818.2 源碼: https://github.com/iOSlixiang/objc4-818.2.git
Class
Objective-C類是由Class類型來表示的,它實(shí)際上只是objc_class的結(jié)構(gòu)體指針。
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
在2006年蘋果發(fā)布Objc 2.0,objc_class定義沒有發(fā)生變化,但是結(jié)構(gòu)體發(fā)生了變化。
在Objc2.0之前,objc_class源碼
// 一個(gè)類的實(shí)例的結(jié)構(gòu)體
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// 類結(jié)構(gòu)體
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
在Objc2.0,objc_class繼承于objc_object。在objc_class中也會(huì)包含isa_t類型的結(jié)構(gòu)體isa。 Objective-C 中類也是一個(gè)對(duì)象。
struct objc_object {
private:
isa_t isa;
}
//繼承
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
}
// isa是一個(gè)聯(lián)合體
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}

把源碼的定義轉(zhuǎn)化成類圖,就是上圖的樣子。
從上述源碼中,我們可以看到,Objective-C 對(duì)象都是 C 語言結(jié)構(gòu)體實(shí)現(xiàn)的,在objc2.0中,所有的對(duì)象都會(huì)包含一個(gè)isa_t類型的結(jié)構(gòu)體。
objc_object被源碼typedef成了id類型,這也就是我們平時(shí)遇到的id類型。這個(gè)結(jié)構(gòu)體中就只包含了一個(gè)isa_t類型的結(jié)構(gòu)體。
bjc_class繼承于objc_object。所以在objc_class中也會(huì)包含isa_t類型的結(jié)構(gòu)體isa。至此,可以得出結(jié)論:Objective-C 中類也是一個(gè)對(duì)象。在objc_class中,除了isa之外,還有3個(gè)成員變量,一個(gè)是父類的指針,一個(gè)是方法緩存,最后一個(gè)這個(gè)類的實(shí)例方法鏈表。
object類和NSObject類里面分別都包含一個(gè)objc_class類型的isa。
上圖的左半邊類的關(guān)系,右邊是isa的結(jié)構(gòu)體
isa指針
- 聯(lián)合體:一種特殊的數(shù)據(jù)類型,其目的是節(jié)省內(nèi)存。聯(lián)合體內(nèi)部可以定義多種數(shù)據(jù)類型,但是同一時(shí)間只能表示某一種數(shù)據(jù)類型,且所有的數(shù)據(jù)類型共享同一段內(nèi)存。聯(lián)合體的內(nèi)存大小等于所定義的數(shù)據(jù)類型中占用內(nèi)存的最大者。
- 互斥賦值/共用內(nèi)存:允許裝入該“聯(lián)合”所定義的任何一種數(shù)據(jù)成員,但同一時(shí)間只能表示一種數(shù)據(jù)成員,采用了覆蓋的技術(shù);
- union所占內(nèi)存長度:union 變量所占用的內(nèi)存長度等于最長的成員的內(nèi)存長度;
union isa_t { //聯(lián)合體
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,兩者是互斥關(guān)系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t類型使用聯(lián)合體的原因也是基于內(nèi)存優(yōu)化的考慮,這里的內(nèi)存優(yōu)化是指在isa指針中通過char + 位域(即二進(jìn)制中每一位均可表示不同的信息)的原理實(shí)現(xiàn)。通常來說,isa指針占用的內(nèi)存大小是8字節(jié),即64位,已經(jīng)足夠存儲(chǔ)很多的信息了,這樣可以極大的節(jié)省內(nèi)存,以提高性能
從isa_t的定義中可以看出:
-
提供了兩個(gè)成員,
cls和bits,由聯(lián)合體的定義所知,這兩個(gè)成員是互斥的,也就意味著,當(dāng)初始化isa指針時(shí),有兩種初始化方式通過
cls初始化,bits無默認(rèn)值通過
bits初始化,cls無默認(rèn)值
還提供了一個(gè)結(jié)構(gòu)體定義的·位域·,用于存儲(chǔ)類信息及其他信息,結(jié)構(gòu)體的成員·ISA_BITFIELD·,這是一個(gè)宏定義,有兩個(gè)版本
__arm64__(對(duì)應(yīng)ios 移動(dòng)端) 和__x86_64__(對(duì)應(yīng)macOS)

元類(Meta Class)
在上面我們提到,所有的類自身也是一個(gè)對(duì)象,我們可以向這個(gè)對(duì)象發(fā)送消息(即調(diào)用類方法)。如:
NSArray *array = [NSArray array];
這個(gè)例子中,+array消息發(fā)送給了NSArray類,而這個(gè)NSArray也是一個(gè)對(duì)象。既然是對(duì)象,那么它也是一個(gè)objc_object指針,它包含一個(gè)指向其類的一個(gè)isa指針。那么這些就有一個(gè)問題了,這個(gè)isa指針指向什么呢?為了調(diào)用+array方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體。這就引出了meta-class的概念
meta-class是一個(gè)類對(duì)象的類。
當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí),會(huì)在這個(gè)類的meta-class的方法列表中查找。
meta-class之所以重要,是因?yàn)樗?code>儲(chǔ)著一個(gè)類的所有類方法。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
再深入一下,meta-class也是一個(gè)類,也可以向它發(fā)送一個(gè)消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個(gè)完美的閉環(huán)。
通過上面的描述,再加上對(duì)objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了,如下圖所示:

圖中實(shí)線是 super_class指針,虛線是isa指針。
- Root class (class)其實(shí)就是NSObject,
NSObject是沒有超類的,所以Root class(class)的superclass指向nil。 - 每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class
- Root class(meta)的superclass指向
Root class(class),也就是NSObject,形成一個(gè)回路。 - 每個(gè)Meta class的isa指針都指向Root class (meta)。
對(duì)于NSObject繼承體系來說,其實(shí)例方法對(duì)體系中的所有實(shí)例、類和meta-class都是有效的;而類方法對(duì)于體系內(nèi)的所有類和meta-class都是有效的。
cache_t
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}

根據(jù)源碼,我們可以知道cache_t中存儲(chǔ)了一個(gè)bucket_t的結(jié)構(gòu)體,和兩個(gè)unsigned int的變量。
mask:分配用來緩存bucket的總數(shù)。
occupied:表明目前實(shí)際占用的緩存bucket的個(gè)數(shù)。
bucket_t的結(jié)構(gòu)體中存儲(chǔ)了一個(gè)unsigned long和一個(gè)IMP。IMP是一個(gè)函數(shù)指針,指向了一個(gè)方法的具體實(shí)現(xiàn)。
cache_t中的bucket_t *_buckets其實(shí)就是一個(gè)散列表,用來存儲(chǔ)Method的鏈表。
Cache的作用主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類,然后在類的methodLists中搜索方法,如果沒有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒有找到,再到methodLists查找。
class_data_bits_t
// class_data_bits_t相當(dāng)于 class_rw_t指針加上 rr/alloc 的標(biāo)志。
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}
// 返回 class_rw_t *指針的快捷方法
class_rw_t *data() {
return bits.data();
}
// class_ro_t 在編譯期產(chǎn)生,它是類中的可讀信息。
// class_rw_t 在運(yùn)行時(shí)產(chǎn)生,它是類中的可讀寫信息。
struct class_rw_t {
uint32_t flags; // 標(biāo)記
uint32_t version; // 版本
// 類中的只讀信息
const class_ro_t *ro;
/*
這三個(gè)都是二位數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容。
methods中,存儲(chǔ) method_list_t ----> method_t
二維數(shù)組,method_list_t --> method_t
這三個(gè)二位數(shù)組中的數(shù)據(jù)有一部分是從class_ro_t中合并過來的。
*/
method_array_t methods; // 方法數(shù)組
property_array_t properties; // 屬性數(shù)組
protocol_array_t protocols; // 協(xié)議數(shù)組
Class firstSubclass; //為某個(gè)類第一次創(chuàng)建的時(shí)候去尋找父類的時(shí)候綁定給父類的。
Class nextSiblingClass; // 下一個(gè)相同父類的類
char *demangledName;
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart; // 開始位置
uint32_t instanceSize; // 所占空間大小
#ifdef __LP64__
uint32_t reserved;
#endif
/* ivar 布局, 在編譯期這里就固定了,
它標(biāo)示ivars的內(nèi)存布局,在運(yùn)行時(shí)不能改變,
這也是為什么我們在運(yùn)行時(shí)不能動(dòng)態(tài)給類添加成員變量的原因*/
const uint8_t * ivarLayout;
const char * name; //名字
method_list_t * baseMethodList; //方法鏈表
protocol_list_t * baseProtocols; //協(xié)議鏈表
const ivar_list_t * ivars; //成員變量鏈表
const uint8_t * weakIvarLayout; // weak 成員變量的內(nèi)存布局
property_list_t *baseProperties; //屬性鏈表
method_list_t *baseMethods() const {
return baseMethodList;
}
};

Objc的類的屬性、方法、以及遵循的協(xié)議在obj 2.0的版本之后都放在class_rw_t中。class_ro_t是一個(gè)指向常量的指針,存儲(chǔ)來編譯器決定了的屬性、方法和遵守協(xié)議。rw-readwrite,ro-readonly
在編譯期類的結(jié)構(gòu)中的 class_data_bits_t *data指向的是一個(gè) class_ro_t *指針。
在運(yùn)行時(shí)調(diào)用 realizeClass方法,會(huì)做以下3件事情:
- 從 class_data_bits_t調(diào)用 data方法,將結(jié)果從 class_rw_t強(qiáng)制轉(zhuǎn)換為 class_ro_t指針
- 初始化一個(gè) class_rw_t結(jié)構(gòu)體
- 設(shè)置結(jié)構(gòu)體 ro的值以及 flag
最后調(diào)用methodizeClass方法,把類里面的屬性,協(xié)議,方法都加載進(jìn)來。
整個(gè)流程 objc_readClassPair --> readClass --> realizeClass --> methodizeClass
總結(jié)一下objc_class 1.0和2.0的差別。
