提前準(zhǔn)備
為了探究對象在底層的實現(xiàn)方式,此次使用clang
來進(jìn)行探究。相關(guān)語句clang -rewrite-objc main.m -o main.cpp
,在終端中進(jìn)入工程目錄main.m
所在的路徑中,執(zhí)行上面的語句蝶防,就會生成一個main.cpp
的C++文件,該文件就是main.m
文件的底層實現(xiàn)方式明吩。
main.cpp探究
通過clang之后的main.cpp 文件可以看到如下代碼:
struct YKPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
可以看出我們創(chuàng)建的YKPerson
類在底部被編譯成了YKPerson_IMPL
的結(jié)構(gòu)體间学,name
屬性成為結(jié)構(gòu)體的屬性。所以我們可以得到第一個結(jié)論印荔,對象在底層被編譯成了結(jié)構(gòu)體低葫,對象在底層是以結(jié)構(gòu)體的形式存在
,而NSObject_ IVARS
是在內(nèi)部默認(rèn)全部都有的屬性isa
仍律。
聯(lián)合體位域
在日常開發(fā)中少不了在類中聲明一些布爾或其他類型的屬性用于判斷嘿悬。為了更高的利用內(nèi)存,我們可以合理使用聯(lián)合體位域的方式來減少對內(nèi)存的占用水泉。
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
union {
char bits;
struct {
char front : 1;
char back : 2;
char left : 3;
char right : 4;
}
} _direction;
例如以上代碼善涨,日常開發(fā)中一般會使用第一段代碼聲明(front,back,left,right)
四個屬性來做相應(yīng)的方向判斷,并且在相應(yīng)的setter草则,getter方法中進(jìn)行相應(yīng)的操作钢拧,因為BOOL
類型是4字節(jié)類型,所以它們需要占用4*4 = 16字節(jié) = 128位
炕横,只是為了做一個方向判斷時源内,這個內(nèi)存占用量其實也是不少的。所以這個時候我們可以用到聯(lián)合體位域
看锉。
在第二段代碼中姿锭,因為char
類型的數(shù)據(jù)為1字節(jié),所以總共占用了5字節(jié)伯铣。每一個屬性后面的數(shù)字代表的是所在的位置(例如:0000 1111,第一位代表front轮纫,第二位代表back腔寡,第三位代表3,第四位代表right)掌唾。使用聯(lián)合體位域時需要配合相應(yīng)的方法來對聯(lián)合體位域中的屬性進(jìn)行相應(yīng)設(shè)置放前。
- 結(jié)構(gòu)體
優(yōu)點:所有的變量是“共存”的忿磅,全面;
缺點:struct內(nèi)存空間的分配是粗放的凭语,不管用不用葱她,全分配。 - 聯(lián)合體
優(yōu)點:各個變量是“互斥”的似扔,內(nèi)存使用更為精細(xì)靈活吨些,節(jié)省了內(nèi)存空間;
缺點:不夠“包容”
isa分析
之前我們已經(jīng)分析了對象在alloc時調(diào)用instanceSize
計算內(nèi)存空間炒辉,calloc
像系統(tǒng)申請內(nèi)存豪墅,接下來就是講開辟的內(nèi)存與對象類進(jìn)行關(guān)聯(lián)。通過源碼分析黔寇,調(diào)用initInstanceIsa
后在方法initIsa
中進(jìn)行處理偶器,因此initIsa
為核心方法。在該方法中主要是對isa
的賦值操作,接下來對該方法做主要分析缝裤。屬性isa
為isa_t
類型屏轰,在該方法中對聯(lián)合體isa_t isa
中的相關(guān)屬性進(jìn)行了一些賦值操作。
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
}
以上為聯(lián)合體isa_t
的定義憋飞,屬性cls
與bits
互斥亭枷,只有當(dāng)nonpointer
不存在時cls
才會被賦值,當(dāng)nonpointer
存在時搀崭,講給bit
賦值一個宏定義的ISA_INDEX_MAGIC_VALUE 0x001d800000000001ULL
叨粘。重點在ISA_BITFIELD
,以下是以x86架構(gòu)下的關(guān)于相關(guān)定義瘤睹,并對內(nèi)部結(jié)構(gòu)進(jìn)行解讀升敲。
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
-
nonpointer
:表示是否對isa
指針開啟指針優(yōu)化,0:純isa指針轰传,1:不止是類對象地址驴党,isa包含了類信息、對象的引用計數(shù)等 -
has_assoc
: 關(guān)聯(lián)對象標(biāo)志位获茬,0沒有港庄,1存在 -
has_cxx_dtor
: 該對象是否有C++或者OBJC的析構(gòu)器,如果有析構(gòu)函數(shù)恕曲,則需要做析構(gòu)邏輯鹏氧,如果沒有,則可以更快的釋放對象 -
shiftcls
:在x86架構(gòu)下占用44位內(nèi)存佩谣。存儲類指針的值把还。開啟指針優(yōu)化的情況下,arm64架構(gòu)下占用33位內(nèi)存。 -
magic
:用于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間吊履。 -
weakly_referenced
:指對象是否被指向或者曾經(jīng)指向一個ARC的弱變量安皱,沒有弱引用的對象可以更快釋放。 -
deallocating
:標(biāo)志對象是否正在釋放內(nèi)存艇炎。 -
has_sidetable_rc
:當(dāng)對象引用計數(shù)大于10時酌伊,則需要借用該變量存儲進(jìn)位。 -
extra_rc
:當(dāng)表示該對象的引用計數(shù)時缀踪,實際上是引用計數(shù)值減1居砖,例如,如果對象的引用計數(shù)為10辜贵,那么extra_rc
為9.如果引用計數(shù)大于10悯蝉,則需要使用到上面的has_sidetable_rc
。
在之前的文章中講到對象調(diào)用alloc
時托慨,在_class_createInstanceFromZone
中與類進(jìn)行關(guān)聯(lián)鼻由,通過調(diào)試obj
為返回結(jié)果,0x001d8001000020f1
為isa
厚棵,我們將isa
進(jìn)行位移計算蕉世,目的是得到isa_t
中的shiftcls
,再以16進(jìn)制打印傳入的cls
時我們發(fā)現(xiàn)此時的shiftcls
就是我們的類婆硬,代表此時我們自定義的類已經(jīng)被系統(tǒng)關(guān)聯(lián)狠轻,并初始化了內(nèi)存空間。
拓展 (clang)
-
clang -rewrite-objc main.m -o main.cpp
把目標(biāo)文件編譯成c++文件 - UIKit報錯問題
-
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platfroms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
通過找到當(dāng)前版本Xcode下對應(yīng)的SDK來進(jìn)行生成彬犯,一般在頭文件中包含UIKit
時出現(xiàn)該問題
-
-
xcode
安裝的時候順帶安裝了xcrun
命令向楼,xcrun
命令在clang
的基礎(chǔ)上進(jìn)行了 一些封裝,要更好用一些 -
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
(模擬器) xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手機(jī))
結(jié)論
從以上可以得到在調(diào)用initIsa時是將系統(tǒng)為類創(chuàng)建的內(nèi)存以及設(shè)置一些類的信息并與當(dāng)前類進(jìn)行關(guān)聯(lián)谐区。所以在isa
中保存了類的所有信息湖蜕。