isa_t類型詳解
在新版的runtime源碼中辈挂,NSObject類型最終會轉(zhuǎn)化為object_class類型衬横,而object_class集成自objc_object,在結(jié)構(gòu)體objc_object中就含有isa_t類型的成員isa
查看isa_t的源碼,其中處理兩個構(gòu)造函數(shù)外终蒂,有一個cls指針蜂林,還有一個uintptr_t類型的成員bits以及一個結(jié)構(gòu)體:
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
}
查看結(jié)構(gòu)體的源碼可以發(fā)現(xiàn),在結(jié)構(gòu)體中使用位域來存儲了很多信息后豫,此處展示arm64架構(gòu)下的源碼信息
#define ISA_MASK 0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK 0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
};
isa_t位域存放信息類型
isa_t作為公用體悉尾,內(nèi)部使用8個字節(jié)的內(nèi)存空間突那,共64位二進(jìn)制挫酿,存放了以下信息
* nonpointer代表是否是優(yōu)化過的isa指針,占用1位愕难。
* 1:表示新版本的isa指針早龟,使用位域來存儲信息
* 0:舊版本普通的isa指針,直接存儲Class和Meta-Class內(nèi)存地址
* has_assoc代表是否有關(guān)聯(lián)對象猫缭,占用1位葱弟,一旦設(shè)置過關(guān)聯(lián)對象,則會置為1猜丹。如果添加過關(guān)聯(lián)對象芝加,在釋放時會檢測是否有關(guān)聯(lián)對象,所以釋放會更慢射窒。
* has_cxx_dtor代表是否實現(xiàn)了C++的析構(gòu)函數(shù)(.cxx_destruct)藏杖,如果沒有将塑,釋放時的速度會更快。占用1位
* shiftcls中存放著類或者元類的內(nèi)存地址蝌麸,占用33位点寥。
* magic是調(diào)試時用來判斷對象是否完成初始化,占用6位
* weakly_referenced代表是否被弱引用指向過来吩,占用1位敢辩,如果為0,則釋放時速度會更快
* deallocating用來表示對象是否正在釋放弟疆,占用1位
* extra_rc用來存儲引用計數(shù)的值戚长,占用19位,此處需要注意的時兽间,它存儲的是引用計數(shù)的值-1历葛。如果對象的引用計數(shù)為1,則extra_rc中存儲的值為0
* has_sidetable_rc用來表示是否將引用計數(shù)存儲在SideTable中嘀略,引用計數(shù)的值過大恤溶,在extra_rc無法存儲,則會將引用計數(shù)存放到SideTable當(dāng)中帜羊。
Class底層結(jié)構(gòu)分析
* 上面我們已經(jīng)分析來isa_t的結(jié)構(gòu)
struct objc_object {
private:
isa_t isa;
public:
......
}
* objc_class繼承自結(jié)構(gòu)體objc_object,結(jié)構(gòu)可以簡化如下
struct objc_class{
Class ISA; //isa指針咒程,通過位域存放多個信息
Class superclass; //supperClass
cache_t cache; // 方法緩存
class_data_bits_t bits; // 用來獲取類的具體信息
}
ojbc_class 除了有isa指針外,還保存了父類的class讼育,方法緩存以及當(dāng)前類的一些基本信息帐姻。
查找class_data_bits_t
- 其實一開始是沒有class_rw_t的,而存放的是class_ro_t,class_rw_t是在之后進(jìn)行創(chuàng)建的奶段。
- class_rw_t是可讀可寫的饥瓷,它包含了類的初始內(nèi)容,分類的內(nèi)容痹籍。
- class_ro_t是只讀的呢铆,它包含了類初始化的內(nèi)容,并且在編譯完成后就決定了蹲缠,在運行時無法進(jìn)行修改棺克。
源碼分析
上文提到,在類初始化的時候其實class中保存的是class_ro_t而不是class_rw_t线定,這一點可以通過objc-runtime-new.mm中的realizeClassWithoutSwift函數(shù)可以看出
static Class realizeClassWithoutSwift(Class cls){
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
//如果class已經(jīng)初始化娜谊,則直接返回當(dāng)前class
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
//首先通過class的data()函數(shù)取到class中bits中存放的class_ro_t
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
//如果當(dāng)前的cls是future class,并且rw已經(jīng)被創(chuàng)建斤讥,則直接拿到rw和rw中的ro
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
//如果是普通的class纱皆,創(chuàng)建rw
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
//將ro賦值給rw中的ro
rw->ro = ro;
//設(shè)置rw的flags
rw->flags = RW_REALIZED|RW_REALIZING;
//將rw設(shè)置到cls中的bits中去
cls->setData(rw);
}
......
//遞歸初始化父類
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
//遞歸初始化元類,通過isa指針來獲取到cls的元類
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
......
//修改rw中的方法列表,屬性列表和協(xié)議列表派草,并且將分類中的方法列表撑帖,屬性列表和協(xié)議列表附加到rw中去
methodizeClass(cls);
}
在類初始化時,cls通過data()函數(shù)獲取到的其實是class_to_t,內(nèi)部存放了類初始的方法列表澳眷、屬性列表和協(xié)議列表胡嘿。如果當(dāng)前cls是普通的class,則通過calloc函數(shù)創(chuàng)建rw钳踊,然后將rw中的ro指針指向原始的ro(class_ro_t),之后重置rw中的flags衷敌,并將rw的內(nèi)存地址保存到cls的bits中去。并且拓瞪,函數(shù)中首先是通過遞歸初始化當(dāng)前父類以及元類缴罗。最后才初始化當(dāng)前類。
創(chuàng)建完rw(class_rw_t)后祭埂,則會重新整理cls中的方法列表面氓、屬性列表和協(xié)議列表。具體查看methodizeClass函數(shù)源碼:
static void methodizeClass(Class cls){
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// 從ro中拿到baseMethodList
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
//將baseMethodList附加到rw的methods中去
rw->methods.attachLists(&list, 1);
}
// 從ro中拿到baseProperties
property_list_t *proplist = ro->baseProperties;
if (proplist) {
//將baseProperties附加到rw的properties中去
rw->properties.attachLists(&proplist, 1);
}
//從ro中拿到baseProtocols
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
//將baseProtocols附加到rw的protocols中去
rw->protocols.attachLists(&protolist, 1);
}
//最后將所有Category的方法列表蛆橡、屬性列表和協(xié)議列表附加到cls
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
methodizeClass 函數(shù)中首先會拿到ro中的方法列表舌界、屬性列表和協(xié)議列表,然后將拿到的方法泰演、屬性列表和協(xié)議列表通過對應(yīng)的attachLists函數(shù)附加到rw的二維數(shù)組中去呻拌。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
//這里以方法列表為例
//array()->lists表示原來類中的方法列表
//addedLists表示所有Category中的方法列表
if (hasArray()) {
//獲取原來類中方法列表的長度
uint32_t oldCount = array()->count;
//得到方法合并之后的新的數(shù)組長度
uint32_t newCount = oldCount + addedCount;
//給array重新分配長度為newCount的內(nèi)存空間
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//將原來array()->lists中的數(shù)據(jù)移動到數(shù)組中oldCount的位置
//也就是相當(dāng)于將array()->lists的數(shù)據(jù)在內(nèi)存中往后移動了addedCount個位置
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//將Category中的方法列表copy到array()->lists中
//并且是從數(shù)組的起始地址開始存放
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
安裝完類本身的方法、屬性和協(xié)議后睦焕,會繼續(xù)通過attachCategories函數(shù)拿到class的所有Category中的方法藐握、屬性和協(xié)議列表,然后調(diào)用attachLists函數(shù)附加到rw中的二維數(shù)組中去垃喊。
//將方法列表猾普、屬性列表、協(xié)議列表附加到類中去
//假設(shè)cats中的所有的類別都是按順序進(jìn)行加載和排序的本谜,最早裝載進(jìn)內(nèi)存的類別是第一個
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
//用來判斷是否是元類
bool isMeta = cls->isMetaClass();
//申請連續(xù)內(nèi)存空間初家,創(chuàng)建一個二維數(shù)組,里面存放著所有的method_list_t
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//申請連續(xù)內(nèi)存空間耕突,創(chuàng)建一個二維數(shù)組笤成,里面存放著所有的property_list_t
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//申請連續(xù)內(nèi)存空間评架,創(chuàng)建一個二維數(shù)組眷茁,里面存放著所有的protocol_list_t
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
//獲取到category_list之后,通過逆序遍歷來取出Category內(nèi)部的方法纵诞、屬性和協(xié)議列表
while (i--) {
auto& entry = cats->list[i];
//遍歷cls所有的category_t上祈,將category_t中的method_list_t取出,存放到二維數(shù)組mlists中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 將category_t中的property_list_t取出,存放到二維數(shù)組proplists中
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//將category_t中的protocol_list_t取出登刺,存放到二維數(shù)組protolists中
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//拿到類對象cls的class_rw_t類型的成員data籽腕,它是可讀可寫的
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//將方法列表合并到rw的方法列表中去,并且插入到表頭位置
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//將屬性列表合并到rw的屬性列表中去纸俭,并且插入到表頭位置
rw->properties.attachLists(proplists, propcount);
free(proplists);
//將協(xié)議列表合并到rw的協(xié)議列表中去皇耗,并且插入到表頭位置
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
因為是先附加類本身的方法、屬性和協(xié)議揍很,之后附加Category的方法郎楼、屬性和協(xié)議,并且attachLists操作從數(shù)組的頭部開始進(jìn)行附加窒悔,所以先執(zhí)行附加操作的方法呜袁、屬性和協(xié)議會放到數(shù)組的后面,因此類本身實現(xiàn)的方法简珠、屬性和協(xié)議肯定放在rw二維數(shù)組的最后一個元素阶界。
說一下對isa指針的理解
isa等價于 is kind of
- 實例對象isa指向類對象
- 類對象isa指向元類對象
- 元類對象isa指向元類的基類
isa有兩種類型
* 純指針,指向內(nèi)存地址 Class 和 metaClass的內(nèi)存地址
* NON_POINTER_ISA聋庵,除了內(nèi)存地址外膘融,還存放了一些其他的信息