(轉(zhuǎn)載)Blog:http://draveness.me/
關(guān)注倉(cāng)庫(kù),及時(shí)獲得更新:iOS-Source-Code-Analyze
因?yàn)?ObjC 的 runtime 只能在 Mac OS 下才能編譯,所以文章中的代碼都是在 Mac OS,也就是x86_64架構(gòu)下運(yùn)行的,對(duì)于在 arm64 中運(yùn)行的代碼會(huì)特別說(shuō)明。
如果你曾經(jīng)對(duì) ObjC 底層的實(shí)現(xiàn)有一定的了解,你應(yīng)該會(huì)知道Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體,所有的對(duì)象都包含一個(gè)類型為isa的指針,那么你可能確實(shí)對(duì) ObjC 的底層有所知,不過現(xiàn)在的 ObjC 對(duì)象的結(jié)構(gòu)已經(jīng)不是這樣了。代替isa指針的是結(jié)構(gòu)體isa_t, 這個(gè)結(jié)構(gòu)體中"包含"了當(dāng)前對(duì)象指向的類的信息,這篇文章中會(huì)介紹一些關(guān)于這個(gè)變化的知識(shí)。
structobjc_object{isa_t isa;};
當(dāng) ObjC 為為一個(gè)對(duì)象分配內(nèi)存,初始化實(shí)例變量后,在這些對(duì)象的實(shí)例變量的結(jié)構(gòu)體中的第一個(gè)就是isa。

所有繼承自NSObject的類實(shí)例化后的對(duì)象都會(huì)包含一個(gè)類型為isa_t的結(jié)構(gòu)體。
從上圖中可以看出,不只是實(shí)例會(huì)包含一個(gè)isa結(jié)構(gòu)體,所有的類也有這么一個(gè)isa。在 ObjC 中 Class 的定義也是一個(gè)名為objc_class的結(jié)構(gòu)體,如下:
structobjc_class:objc_object{isa_t isa;Class superclass;cache_t cache;class_data_bits_t bits;};
由于objc_class結(jié)構(gòu)體是繼承自objc_object的,所以在這里顯式地寫出了isa_t isa這個(gè)成員變量。
isa指針的作用與元類
到這里,我們就明白了:Objective-C 中類也是一個(gè)對(duì)象。
這個(gè)isa包含了什么呢?回答這個(gè)問題之前,要引入了另一個(gè)概念元類(meta class),我們先了解一些關(guān)于元類的信息。
因?yàn)樵?Objective-C 中,對(duì)象的方法并沒有存儲(chǔ)于對(duì)象的結(jié)構(gòu)體中(如果每一個(gè)對(duì)象都保存了自己能執(zhí)行的方法,那么對(duì)內(nèi)存的占用有極大的影響)。
當(dāng)實(shí)例方法被調(diào)用時(shí),它要通過自己持有的isa來(lái)查找對(duì)應(yīng)的類,然后在這里的class_data_bits_t結(jié)構(gòu)體中查找對(duì)應(yīng)方法的實(shí)現(xiàn)。同時(shí),每一個(gè)objc_class也有一個(gè)指向自己的父類的指針super_class用來(lái)查找繼承的方法。
關(guān)于如何在class_data_bits_t中查找對(duì)應(yīng)方法會(huì)在之后的文章中講到。這里只需要知道,它會(huì)在這個(gè)結(jié)構(gòu)體中查找到對(duì)應(yīng)方法的實(shí)現(xiàn)就可以了。

但是,這樣就有一個(gè)問題,類方法的實(shí)現(xiàn)又是如何查找并且調(diào)用的呢?這時(shí),就需要引入元類來(lái)保證無(wú)論是類還是對(duì)象都能通過相同的機(jī)制查找方法的實(shí)現(xiàn)。

讓每一個(gè)類的isa指向?qū)?yīng)的元類,這樣就達(dá)到了使類方法和實(shí)例方法的調(diào)用機(jī)制相同的目的:
實(shí)例方法調(diào)用時(shí),通過對(duì)象的isa在類中獲取方法的實(shí)現(xiàn)
類方法調(diào)用時(shí),通過類的isa在元類中獲取方法的實(shí)現(xiàn)
下面這張圖介紹了對(duì)象,類與元類之間的關(guān)系,筆者認(rèn)為已經(jīng)覺得足夠清晰了,所以不在贅述。

圖片來(lái)自objcexplainClassesandmetaclasses
有關(guān)與介紹類與元類之間的關(guān)系的文章實(shí)在是太多了,因?yàn)檫@篇文章主要介紹isa,在這一小節(jié)只是對(duì)其作用以及元類的概念進(jìn)行介紹。如果想要了解更多關(guān)于類與元類的信息,可以看What is a meta-class in Objective-C?
結(jié)構(gòu)體isa_t
其實(shí)isa_t是一個(gè)定義得非常"奇怪"的結(jié)構(gòu)體,在 ObjC 源代碼中可以看到這樣的定義:
#define ISA_MASK? ? ? ? 0x00007ffffffffff8ULL#define ISA_MAGIC_MASK? 0x001f800000000001ULL#define ISA_MAGIC_VALUE 0x001d800000000001ULL#define RC_ONE? (1ULL<<56)#define RC_HALF? (1ULL<<7)unionisa_t{isa_t(){}isa_t(uintptr_t value):bits(value){}Class cls;uintptr_t bits;struct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:44;uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:8;};};
這是在__x86_64__上的實(shí)現(xiàn),對(duì)于 iPhone5s 等架構(gòu)為__arm64__的設(shè)備上,具體結(jié)構(gòu)體的實(shí)現(xiàn)和位數(shù)可能有些差別,不過這些字段都是存在的,可以看這里的arm64 上結(jié)構(gòu)體的實(shí)現(xiàn)
在本篇文章中, 我們會(huì)以__x86_64__為例進(jìn)行分析,而不會(huì)對(duì)兩種架構(gòu)下由于不同的內(nèi)存布局方式導(dǎo)致的差異進(jìn)行分析。在我看來(lái),這個(gè)細(xì)節(jié)不會(huì)影響對(duì)isa指針的理解,不過還是要知道的。
筆者對(duì)這個(gè)isa_t的實(shí)現(xiàn)聲明順序有一些更改,更方便分析和理解。
unionisa_t{...};
isa_t是一個(gè)union類型的結(jié)構(gòu)體,對(duì)union不熟悉的讀者可以看這個(gè) stackoverflow 上的回答. 也就是說(shuō)其中的isa_t、cls、bits還有結(jié)構(gòu)體共用同一塊地址空間。而isa總共會(huì)占據(jù) 64 位的內(nèi)存空間(決定于其中的結(jié)構(gòu)體)

struct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:44;uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:8;};
isa的初始化
我們可以通過isa初始化的方法initIsa來(lái)初步了解這 64 位的 bits 的作用:
inlinevoidobjc_object::initInstanceIsa(Class cls,bool hasCxxDtor){initIsa(cls,true,hasCxxDtor);}inlinevoidobjc_object::initIsa(Class cls,bool indexed,bool hasCxxDtor){if(!indexed){isa.cls=cls;}else{isa.bits=ISA_MAGIC_VALUE;isa.has_cxx_dtor=hasCxxDtor;isa.shiftcls=(uintptr_t)cls>>3;}}
indexed和magic
當(dāng)我們對(duì)一個(gè) ObjC 對(duì)象分配內(nèi)存時(shí),其方法調(diào)用棧中包含了上述的兩個(gè)方法,這里關(guān)注的重點(diǎn)是initIsa方法,由于在initInstanceIsa方法中傳入了indexed = true,所以,我們簡(jiǎn)化一下這個(gè)方法的實(shí)現(xiàn):
inlinevoidobjc_object::initIsa(Class cls,bool indexed,bool hasCxxDtor){isa.bits=ISA_MAGIC_VALUE;isa.has_cxx_dtor=hasCxxDtor;isa.shiftcls=(uintptr_t)cls>>3;}
對(duì)整個(gè)isa的值bits進(jìn)行設(shè)置,傳入ISA_MAGIC_VALUE:
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
我們可以把它轉(zhuǎn)換成二進(jìn)制的數(shù)據(jù),然后看一下哪些屬性對(duì)應(yīng)的位被這行代碼初始化了(標(biāo)記為紅色):

從圖中了解到,在使用ISA_MAGIC_VALUE設(shè)置isa_t結(jié)構(gòu)體之后,實(shí)際上只是設(shè)置了indexed以及magic這兩部分的值。
其中indexed表示isa_t的類型
0 表示raw isa,也就是沒有結(jié)構(gòu)體的部分,訪問對(duì)象的isa會(huì)直接返回一個(gè)指向cls的指針,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型。
union isa_t {? ? isa_t() { }? ? isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
};
1 表示當(dāng)前isa不是指針,但是其中也有cls的信息,只是其中關(guān)于類的指針都是保存在shiftcls中。
union isa_t {? ? isa_t() { }? ? isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed? ? ? ? ? : 1;
uintptr_t has_assoc? ? ? ? : 1;
uintptr_t has_cxx_dtor? ? ? : 1;
uintptr_t shiftcls? ? ? ? ? : 44;
uintptr_t magic? ? ? ? ? ? : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating? ? ? : 1;
uintptr_t has_sidetable_rc? : 1;
uintptr_t extra_rc? ? ? ? ? : 8;
};
};
magic的值為0x3b用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
has_cxx_dtor
在設(shè)置indexed和magic值之后,會(huì)設(shè)置isa的has_cxx_dtor,這一位表示當(dāng)前對(duì)象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒有析構(gòu)器就會(huì)快速釋放內(nèi)存。
isa.has_cxx_dtor=hasCxxDtor;

shiftcls
在為indexed、magic和has_cxx_dtor設(shè)置之后,我們就要將當(dāng)前對(duì)象對(duì)應(yīng)的類指針存入isa結(jié)構(gòu)體中了。
isa.shiftcls=(uintptr_t)cls>>3;
將當(dāng)前地址右移三位的主要原因是用于將 Class 指針中無(wú)用的后三位清楚減小內(nèi)存的消耗,因?yàn)轭惖闹羔樢凑兆止?jié)(8 bits)對(duì)齊內(nèi)存,其指針后三位都是沒有意義的 0。
絕大多數(shù)機(jī)器的架構(gòu)都是byte-addressable的,但是對(duì)象的內(nèi)存地址必須對(duì)齊到字節(jié)的倍數(shù),這樣可以提高代碼運(yùn)行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對(duì)齊的最后三位比特為000,我們只會(huì)用其中的 30 位來(lái)表示對(duì)象的地址。
而 ObjC 中的類指針的地址后三位也為 0,在_class_createInstanceFromZone方法中打印了調(diào)用這個(gè)方法傳入的類指針:

可以看到,這里打印出來(lái)的所有類指針十六進(jìn)制地址的最后一位都為 8 或者 0。也就是說(shuō),類指針的后三位都為 0,所以,我們?cè)谏厦娲鎯?chǔ)Class指針時(shí)右移三位是沒有問題的。
isa.shiftcls=(uintptr_t)cls>>3;
如果再嘗試打印對(duì)象指針的話,會(huì)發(fā)現(xiàn)所有對(duì)象內(nèi)存地址的后四位都是 0,說(shuō)明 ObjC 在初始化內(nèi)存時(shí)是以 16 個(gè)字節(jié)對(duì)齊的, 分配的內(nèi)存地址后四位都是 0。

使用整個(gè)指針大小的內(nèi)存來(lái)存儲(chǔ)isa指針有些浪費(fèi),尤其在 64 位的 CPU 上。在ARM64運(yùn)行的 iOS 只使用了 33 位作為指針(與結(jié)構(gòu)體中的 33 位無(wú)關(guān),Mac OS 上為 47 位),而剩下的 31 位用于其它目的。類的指針也同樣根據(jù)字節(jié)對(duì)齊了,每一個(gè)類指針的地址都能夠被 8 整除,也就是使最后 3 bits 為 0,為isa留下 34 位用于性能的優(yōu)化。
Using an entire pointer-sized piece of memory for the isa pointer is a bit wasteful, especially on 64-bit CPUs which don't use all 64 bits of a pointer. ARM64 running iOS currently uses only 33 bits of a pointer, leaving 31 bits for other purposes. Class pointers are also aligned, meaning that a class pointer is guaranteed to be divisible by 8, which frees up another three bits, leaving 34 bits of the isa available for other uses. Apple's ARM64 runtime takes advantage of this for some great performance improvements. fromARM64 and You
我嘗試運(yùn)行了下面的代碼將NSObject的類指針和對(duì)象的isa打印出來(lái),具體分析一下

object_pointer: 0000000001011101100000000000000100000000001110101110000011111001 // 補(bǔ)全至 64 位
class_pointer:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 100000000001110101110000011111000
編譯器對(duì)直接訪問isa的操作會(huì)有警告,因?yàn)橹苯釉L問isa已經(jīng)不會(huì)返回類指針了,這種行為已經(jīng)被啟用了,取而代之的是使用ISA()方法來(lái)獲取類指針。
代碼中的object對(duì)象的isa結(jié)構(gòu)體中的內(nèi)容是這樣的:

其中紅色的為類指針,與上面打印出的[NSObject class]指針右移三位的結(jié)果完全相同。這也就驗(yàn)證了我們之前對(duì)于初始化isa時(shí)對(duì)initIsa方法的分析是正確的。它設(shè)置了indexed、magic以及shiftcls。
因?yàn)槲覀兪褂媒Y(jié)構(gòu)體取代了原有的 isa 指針,所以要提供一個(gè)方法ISA()來(lái)返回類指針。
其中ISA_MASK是宏定義,這里通過掩碼的方式獲取類指針:
#define ISA_MASK 0x00007ffffffffff8ULLinlineClass? objc_object::ISA(){return(Class)(isa.bits&ISA_MASK);}
其它 bits
在isa_t中,我們還有一些沒有介紹的其它 bits,在這個(gè)小結(jié)就簡(jiǎn)單介紹下這些 bits 的作用
has_assoc
對(duì)象含有或者曾經(jīng)含有關(guān)聯(lián)引用,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
weakly_referenced
對(duì)象被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量,沒有弱引用的對(duì)象可以更快釋放
deallocating
對(duì)象正在釋放內(nèi)存
has_sidetable_rc
對(duì)象的引用計(jì)數(shù)太大了,存不下
extra_rc
對(duì)象的引用計(jì)數(shù)超過 1,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10,extra_rc的值就為 9
struct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:44;uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:8;};
arm64 架構(gòu)中的isa_t結(jié)構(gòu)體
#define ISA_MASK? ? ? ? 0x0000000ffffffff8ULL#define ISA_MAGIC_MASK? 0x000003f000000001ULL#define ISA_MAGIC_VALUE 0x000001a000000001ULL#define RC_ONE? (1ULL<<45)#define RC_HALF? (1ULL<<18)unionisa_t{isa_t(){}isa_t(uintptr_t value):bits(value){}Class cls;uintptr_t bits;struct{uintptr_t indexed:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:33;uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:19;};};
參考資料
Objective-C Runtime Programming Guide
What is a meta-class in Objective-C?