前言
- 這篇主要內(nèi)容探索 類與isa是如何關(guān)聯(lián)的汉额。
- 在之前iOS底層探索 alloc&init這篇文章中粥血,我們知道了_class_createInstanceFromZone方法的關(guān)鍵三步:
a. 獲取實(shí)例的內(nèi)存空間大小: cls->instanceSize()
b. 根據(jù)內(nèi)存大小邻遏,分配內(nèi)存空間台猴,讓實(shí)例指向內(nèi)存開始地址: calloc
c. 關(guān)聯(lián)isa,實(shí)例的isa指向類: obj->initInstanceIsa(cls, hasCxxDtor)
叽奥, 結(jié)合位運(yùn)算扔水、聯(lián)合體、位域和結(jié)構(gòu)體的內(nèi)存對齊的知識朝氓,我們探索oc對象的本質(zhì)從關(guān)聯(lián)isa魔市,實(shí)例的isa指向類
開始。
準(zhǔn)備工作
先大概了解一個編譯器:clang
:
-
clang
是一個由Apple主導(dǎo)編寫膀篮,基于LLVM的C/C++/OC的編譯器,主要是用于底層編譯嘹狞,將一些文件``輸出成c++文件,例如main.m 輸出成main.cpp; - 其目的是為了更好的觀察底層的一些結(jié)構(gòu) 及 實(shí)現(xiàn)的邏輯誓竿,方便理解底層原理。
一 谈截、查看類的編譯源碼
1 .打開終端筷屡,cd到文件目錄下涧偷,利用clang將main.m編譯成 main.cpp .
//1、將 main.m 編譯成 main.cpp
clang -rewrite-objc main.m -o main.cpp
其他舉例:
//1毙死、將 ViewController.m 編譯成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下兩種方式是通過指定架構(gòu)模式的命令行燎潮,使用xcode工具 xcrun
//2、模擬器文件編譯
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//3扼倘、真機(jī)文件編譯
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
2 .打開 main.cpp确封, 找到HJPerson,發(fā)現(xiàn)HJPerson在底層會被編譯成 struct 結(jié)構(gòu)體
點(diǎn)擊NSObject_IMPL
跳轉(zhuǎn)可以得到 isa
struct NSObject_IMPL {
Class isa;
};
先看這里:
struct HJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
這里的知識點(diǎn):
-
HJPerson中
的第一個屬性NSObject_IVARS
等效于NSObject
中的isa
, 每個類中都有默認(rèn)屬性isa
再菊;
二 爪喘、翻開objc源碼 可以看到 NSObject
的isa
也是class
類型
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
現(xiàn)在我們通過clang 看到類被編譯成結(jié)構(gòu)體之后,找到isa 纠拔,那么isa是如何關(guān)聯(lián)類的信息的呢秉剑?
我們可以在 _class_createInstanceFromZone
的核心方法的第三步:關(guān)聯(lián)isa,實(shí)例的isa指向類: obj->initInstanceIsa(cls, hasCxxDtor)
中找到答案稠诲,這里做了一系列操作isa和類信息的操作侦鹏,我們再去查看initInstanceIsa
內(nèi)部源碼
inline void
objc_object::initProtocolIsa(Class cls)
{
return initClassIsa(cls);
}
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
//排隊(duì)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
//斷言
ASSERT(!isTaggedPointer());
if (!nonpointer) {
//關(guān)鍵代碼: 初始化isa
isa = isa_t((uintptr_t)cls);
} else {
//禁用非指針I(yè)sa
ASSERT(!DisableNonpointerIsa);
//實(shí)例需要原始Isa
ASSERT(!cls->instancesRequireRawIsa());
//關(guān)鍵代碼: 初始化isa
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic是ISA_MAGIC_VALUE的一部分
// isa.nonpointer是ISA_MAGIC_VALUE的一部分
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
重要代碼:isa_t: 指針的初始化
-
isa = isa_t((uintptr_t)cls)
; -
isa_t newisa(0)
;
關(guān)鍵單詞:nonpointer
非指針
三 、繼續(xù)探索isa_t
是怎么做的臀叙,我們再次進(jìn)入源碼跳轉(zhuǎn)到isa_t
內(nèi)部如下:
union isa_t { //聯(lián)合體 isa_t
//兩個初始化方法
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//一個Class
Class cls;
//一個 bits 略水。 uintptr_t :在64位的機(jī)器上,intptr_t和uintptr_t分別是long int劝萤、unsigned long int的別名
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
// ISA_BITFIELD 這是一個宏
ISA_BITFIELD; // defined in isa.h
};
#endif
};
從union isa_t定義可以看出:
-
提供了兩個成員渊涝,cls 和 bits,由聯(lián)合體的定義所知稳其,這兩個成員是互斥的驶赏,也就意味著,當(dāng)初始化isa指針時既鞠,有兩種初始化方式
通過cls初始化煤傍,bits無默認(rèn)值
通過bits初始化,cls有默認(rèn)值
-
還提供了一個結(jié)構(gòu)體定義的位域嘱蛋,用于存儲類信息及其他信息蚯姆,結(jié)構(gòu)體的成員ISA_BITFIELD,這是一個宏定義洒敏,有兩個版本 arm64(對應(yīng)ios 移動端) 和 x86_64(對應(yīng)macOS)龄恋,以下是它們的一些宏定義,如下圖所示:
注:
nonpointer有兩個值凶伙,表示自定義的類等郭毕,占1位
0:純isa指針
1:不只是類對象地址,isa中包含了類信息函荣、對象的引用計(jì)數(shù)等has_assoc表示關(guān)聯(lián)對象標(biāo)志位显押,占1位
0:沒有關(guān)聯(lián)對象
1:存在關(guān)聯(lián)對象has_cxx_dtor 表示該對象是否有C++/OC的析構(gòu)器(類似于dealloc)扳肛,占1位如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯
如果沒有乘碑,則可以更快的釋放對象挖息, 析構(gòu)函數(shù) 類似于 oc層面的 dealloc;shiftclx表示存儲類的指針的值(類的地址)兽肤, 即類信息arm64中占 33位套腹,開啟指針優(yōu)化的情況下,在arm64架構(gòu)中有33位用來存儲類指針x86_64中占 44位资铡;
magic 用于調(diào)試器判斷當(dāng)前對象是真的對象 還是 沒有初始化的空間电禀,占6位;
weakly_refrenced是 指對象是否被指向 或者 曾經(jīng)指向一個ARC的弱變量害驹, 沒有弱引用的對象可以更快釋放deallocating 標(biāo)志對象是是否正在釋放內(nèi)存鞭呕;
has_sidetable_rc表示 當(dāng)對象引用計(jì)數(shù)大于10時,則需要借用該變量存儲進(jìn)位宛官;
extra_rc(額外的引用計(jì)數(shù)) --- 表示該對象的引用計(jì)數(shù)值葫松,實(shí)際上是引用計(jì)數(shù)值減1, 如果對象的引用計(jì)數(shù)為10底洗,那么extra_rc為9腋么;
針對兩種不同平臺,其isa的存儲情況如圖所示
我們lldb 調(diào)試 可以看到經(jīng)過一系列賦值 將HJPerson類信息存進(jìn)了shiftcls中
注:
為什么在shiftcls賦值時(newisa.shiftcls = (uintptr_t)cls >> 3)
需要類型強(qiáng)轉(zhuǎn)亥揖?因?yàn)閮?nèi)存的存儲不能存儲字符串珊擂,機(jī)器碼只能識別 0 、1這兩種數(shù)字费变,所以需要將其轉(zhuǎn)換為uintptr_t數(shù)據(jù)類型摧扇,這樣shiftcls中存儲的類信息才能被機(jī)器碼理解, 其中uintptr_t是long挚歧。
至此扛稽,我們得出結(jié)論:在isa初始化obj->initInstanceIsa(cls, hasCxxDtor)
的時候,通過isa_t
聯(lián)合體滑负,在位域
運(yùn)算中在张,將類信息cls
存進(jìn)了存儲類的指針的值shiftclx
, 最后isa = newisa
;isa
中既有HJPerson
的指針,又有HJPerson
的信息矮慕。就這樣isa與類關(guān)聯(lián)到一起了帮匾。
四 、拓展 驗(yàn)證 isa 與 類 的關(guān)聯(lián)
簡單點(diǎn) 我們在x86_64
中通過isa & ISA_MSAK
驗(yàn)證
流程:
1.在
_class_createInstanceFromZone
方法痴鳄,此時cl
s 與isa
已經(jīng)關(guān)聯(lián)完成瘟斜,執(zhí)行po objc
2.執(zhí)行
x/4gx obj
,得到isa指針
的地址0x001d8001000021fd
3.將
isa指針地址 & ISA_MASK
(處于macOS
,使用x86_64
中的宏定義),即po 0x001d8001000021fd & 0x00007ffffffffff8
哼转,得出HJPerson
.x86_64
中明未,ISA_MASK
宏定義的值為0x00007ffffffffff8ULL
good~~~加油槽华!