isa的本質(zhì)
在學(xué)習(xí)Runtime之前首先需要對(duì)isa的本質(zhì)有一定的了解,這樣之后學(xué)習(xí)Runtime會(huì)更便于理解。
回顧OC對(duì)象的本質(zhì),每個(gè)OC對(duì)象都含有一個(gè)isa指針,__arm64__
之前狞膘,isa僅僅是一個(gè)指針,保存著對(duì)象或類(lèi)對(duì)象內(nèi)存地址什乙,在__arm64__
架構(gòu)之后挽封,apple對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu)臣镣,同時(shí)使用位域來(lái)存儲(chǔ)更多的信息辅愿。
我們知道OC對(duì)象的isa指針并不是直接指向類(lèi)對(duì)象或者元類(lèi)對(duì)象,而是需要&ISA_MASK
通過(guò)位運(yùn)算才能獲取到類(lèi)對(duì)象或者元類(lèi)對(duì)象的地址忆某。今天來(lái)探尋一下為什么需要&ISA_MASK
才能獲取到類(lèi)對(duì)象或者元類(lèi)對(duì)象的地址点待,以及這樣的好處。
首先在源碼中找到isa指針弃舒,看一下isa指針的本質(zhì)癞埠。
// 截取objc_object內(nèi)部分代碼
struct objc_object {
private:
isa_t isa;
}
isa指針其實(shí)是一個(gè)isa_t類(lèi)型的共用體,來(lái)到isa_t內(nèi)部查看其結(jié)構(gòu)
// 精簡(jiǎn)過(guò)的isa_t共用體
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# 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;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
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)
};
# else
# error unknown architecture for packed isa
# endif
#endif
上述源碼中isa_t
是union類(lèi)型,union表示共用體苗踪〉咔可以看到共用體中有一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部分別定義了一些變量通铲,變量后面的值代表的是該變量占用多少個(gè)二進(jìn)制位瓦呼,也就是位域技術(shù)。
共用體:在進(jìn)行某些算法的C語(yǔ)言編程的時(shí)候测暗,需要使幾種不同類(lèi)型的變量存放到同一段內(nèi)存單元中。也就是使用覆蓋技術(shù)磨澡,幾個(gè)變量互相覆蓋碗啄。這種幾個(gè)不同的變量共同占用一段內(nèi)存的結(jié)構(gòu),在C語(yǔ)言中稳摄,被稱(chēng)作“共用體”類(lèi)型結(jié)構(gòu)稚字,簡(jiǎn)稱(chēng)共用體。
接下來(lái)使用共用體的方式來(lái)深入的了解apple為什么要使用共用體厦酬,以及使用共用體的好處胆描。
探尋過(guò)程
接下來(lái)使用代碼來(lái)模仿底層的做法,創(chuàng)建一個(gè)person類(lèi)并含有三個(gè)BOOL類(lèi)型的成員變量仗阅。
@interface Person : NSObject
@property (nonatomic, assign, getter = isTall) BOOL tall;
@property (nonatomic, assign, getter = isRich) BOOL rich;
@property (nonatomic, assign, getter = isHansome) BOOL handsome;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%zd", class_getInstanceSize([Person class]));
}
return 0;
}
// 打印內(nèi)容
// Runtime - union探尋[52235:3160607] 16
上述代碼中Person含有3個(gè)BOOL類(lèi)型的屬性昌讲,打印Person類(lèi)對(duì)象占據(jù)內(nèi)存空間為16,也就是(isa指針 = 8) + (BOOL tall = 1) + (BOOL rich = 1) + (BOOL handsome = 1) = 13
减噪。因?yàn)閮?nèi)存對(duì)齊原則所以Person類(lèi)對(duì)象占據(jù)內(nèi)存空間為16短绸。
上面提到過(guò)共用體中變量可以相互覆蓋,可以使幾個(gè)不同的變量存放到同一段內(nèi)存單元中筹裕,可以很大程度上節(jié)省內(nèi)存空間醋闭。
那么我們知道BOOL值只有兩種情況 0 或者 1,但是卻占據(jù)了一個(gè)字節(jié)的內(nèi)存空間朝卒,而一個(gè)內(nèi)存空間中有8個(gè)二進(jìn)制位证逻,并且二進(jìn)制只有 0 或者 1 。那么是否可以使用1個(gè)二進(jìn)制位來(lái)表示一個(gè)BOOL值抗斤,也就是說(shuō)3個(gè)BOOL值最終只使用3個(gè)二進(jìn)制位囚企,也就是一個(gè)內(nèi)存空間即可呢?如何實(shí)現(xiàn)這種方式豪治?
首先如果使用這種方式需要自己寫(xiě)方法聲明與實(shí)現(xiàn)洞拨,不可以寫(xiě)屬性,因?yàn)橐坏?xiě)屬性负拟,系統(tǒng)會(huì)自動(dòng)幫我們添加成員變量烦衣。
另外想要將三個(gè)BOOL值存放在一個(gè)字節(jié)中,我們可以添加一個(gè)char
類(lèi)型的成員變量,char
類(lèi)型占據(jù)一個(gè)字節(jié)內(nèi)存空間花吟,也就是8個(gè)二進(jìn)制位秸歧。可以使用其中最后三個(gè)二進(jìn)制位來(lái)存儲(chǔ)3個(gè)BOOL值衅澈。
@interface Person()
{
char _tallRichHandsome;
}
例如_tallRichHansome的值為 0b 0000 0010
键菱,那么只使用8個(gè)二進(jìn)制位中的最后3個(gè),分別為其賦值0或者1來(lái)代表tall今布、rich经备、handsome
的值。如下圖所示
那么現(xiàn)在面臨的問(wèn)題就是如何取出8個(gè)二進(jìn)制位中的某一位的值部默,或者為某一位賦值呢侵蒙?
取值
首先來(lái)看一下取值,假如char類(lèi)型的成員變量中存儲(chǔ)的二進(jìn)制為0b 0000 0010
如果想將倒數(shù)第2位的值也就是rich的值取出來(lái)傅蹂,可以使用&進(jìn)行按位與運(yùn)算進(jìn)而去除相應(yīng)位置的值纷闺。
&:按位與,同真為真份蝴,其他都為假犁功。
// 示例
// 取出倒數(shù)第三位 tall
0000 0010
& 0000 0100
------------
0000 0000 // 取出倒數(shù)第三位的值為0,其他位都置為0
// 取出倒數(shù)第二位 rich
0000 0010
& 0000 0010
------------
0000 0010 // 取出倒數(shù)第二位的值為1婚夫,其他位都置為0
按位與可以用來(lái)取出特定的位浸卦,想取出哪一位就將那一位置為1,其他為都置為0请敦,然后同原數(shù)據(jù)進(jìn)行按位與計(jì)算镐躲,即可取出特定的位。
那么此時(shí)可以將get方法寫(xiě)成如下方式
#define TallMask 0b00000100 // 4
#define RichMask 0b00000010 // 2
#define HandsomeMask 0b00000001 // 1
- (BOOL)tall
{
return !!(_tallRichHandsome & TallMask);
}
- (BOOL)rich
{
return !!(_tallRichHandsome & RichMask);
}
- (BOOL)handsome
{
return !!(_tallRichHandsome & HandsomeMask);
}
上述代碼中使用兩個(gè)!!(非)
來(lái)將值改為bool類(lèi)型侍筛。同樣使用上面的例子
// 取出倒數(shù)第二位 rich
0000 0010 // _tallRichHandsome
& 0000 0010 // RichMask
------------
0000 0010 // 取出rich的值為1萤皂,其他位都置為0
上述代碼中(_tallRichHandsome & TallMask)
的值為0000 0010
也就是2,但是我們需要的是一個(gè)BOOL類(lèi)型的值 0 或者 1 匣椰,那么!!2
就將 2 先轉(zhuǎn)化為 0 裆熙,之后又轉(zhuǎn)化為 1。相反如果按位與取得的值為 0 時(shí)禽笑,!!0
將 0 先轉(zhuǎn)化為 1 之后又轉(zhuǎn)化為 0入录。
因此使用!!
兩個(gè)非操作將值轉(zhuǎn)化為 0 或者 1 來(lái)表示相應(yīng)的值。
掩碼 : 上述代碼中定義了三個(gè)宏佳镜,用來(lái)分別進(jìn)行按位與運(yùn)算而取出相應(yīng)的值僚稿,一般用來(lái)按位與(&)運(yùn)算的值稱(chēng)之為掩碼。
為了能更清晰的表明掩碼是為了取出哪一位的值蟀伸,上述三個(gè)宏的定義可以使用<<(左移)
來(lái)優(yōu)化
<<:表示左移一位蚀同,下圖為例缅刽。
那么上述宏定義可以使用<<(左移)
優(yōu)化成如下代碼
#define TallMask (1<<2) // 0b00000100 4
#define RichMask (1<<1) // 0b00000010 2
#define HandsomeMask (1<<0) // 0b00000001 1
設(shè)值
設(shè)值即是將某一位設(shè)值為0或者1,可以使用|(按位或)
操作符蠢络。
| : 按位或衰猛,只要有一個(gè)1即為1,否則為0刹孔。
如果想將某一位置為1的話(huà)啡省,那么將原本的值與掩碼進(jìn)行按位或的操作即可,例如我們想將tall置為1
// 將倒數(shù)第三位 tall置為1
0000 0010 // _tallRichHandsome
| 0000 0100 // TallMask
------------
0000 0110 // 將tall置為1髓霞,其他位值都不變
如果想將某一位置為0的話(huà)卦睹,需要將掩碼按位取反(~ : 按位取反符),之后在與原本的值進(jìn)行按位與操作即可方库。
// 將倒數(shù)第二位 rich置為0
0000 0010 // _tallRichHandsome
& 1111 1101 // RichMask按位取反
------------
0000 0000 // 將rich置為0分预,其他位值都不變
此時(shí)set方法內(nèi)部實(shí)現(xiàn)如下
- (void)setTall:(BOOL)tall
{
if (tall) { // 如果需要將值置為1 // 按位或掩碼
_tallRichHandsome |= TallMask;
}else{ // 如果需要將值置為0 // 按位與(按位取反的掩碼)
_tallRichHandsome &= ~TallMask;
}
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome |= RichMask;
}else{
_tallRichHandsome &= ~RichMask;
}
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome |= HandsomeMask;
}else{
_tallRichHandsome &= ~HandsomeMask;
}
}
寫(xiě)完set、get方法之后通過(guò)代碼來(lái)查看一下是否可以設(shè)值薪捍、取值成功。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.tall = YES;
person.rich = NO;
person.handsome = YES;
NSLog(@"tall : %d, rich : %d, handsome : %d", person.tall,person.rich,person.handsome);
}
return 0;
}
打印內(nèi)容
Runtime - union探尋[58212:3857728] tall : 1, rich : 0, handsome : 1
可以看出上述代碼可以正常賦值和取值配喳。但是代碼還是有一定的局限性酪穿,當(dāng)需要添加新屬性的時(shí)候,需要重復(fù)上述工作晴裹,并且代碼可讀性比較差被济。接下來(lái)使用結(jié)構(gòu)體的位域特性來(lái)優(yōu)化上述代碼。
位域
將上述代碼進(jìn)行優(yōu)化涧团,使用結(jié)構(gòu)體位域只磷,可以使代碼可讀性更高。
位域聲明 位域名 : 位域長(zhǎng)度;
使用位域需要注意以下3點(diǎn):
- 如果一個(gè)字節(jié)所拭谛澹空間不夠存放另一位域時(shí)钮追,應(yīng)從下一單元起存放該位域。也可以有意使某位域從下一單元開(kāi)始阿迈。
- 位域的長(zhǎng)度不能大于數(shù)據(jù)類(lèi)型本身的長(zhǎng)度元媚,比如int類(lèi)型就不能超過(guò)32位二進(jìn)位。
- 位域可以無(wú)位域名苗沧,這時(shí)它只用來(lái)作填充或調(diào)整位置刊棕。無(wú)名的位域是不能使用的。
上述代碼使用結(jié)構(gòu)體位域優(yōu)化之后待逞。
@interface Person()
{
struct {
char handsome : 1; // 位域甥角,代表占用一位空間
char rich : 1; // 按照順序只占一位空間
char tall : 1;
}_tallRichHandsome;
}
set、get方法中可以直接通過(guò)結(jié)構(gòu)體賦值和取值
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)tall
{
return _tallRichHandsome.tall;
}
- (BOOL)rich
{
return _tallRichHandsome.rich;
}
- (BOOL)handsome
{
return _tallRichHandsome.handsome;
}
通過(guò)代碼驗(yàn)證一下是否可以賦值或取值正確
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.tall = YES;
person.rich = NO;
person.handsome = YES;
NSLog(@"tall : %d, rich : %d, handsome : %d", person.tall,person.rich,person.handsome);
}
return 0;
}
首先在log處打個(gè)斷點(diǎn)识樱,查看_tallRichHandsome內(nèi)存儲(chǔ)的值
因?yàn)?code>_tallRichHandsome占據(jù)一個(gè)內(nèi)存空間嗤无,也就是8個(gè)二進(jìn)制位震束,我們將05十六進(jìn)制轉(zhuǎn)化為二進(jìn)制查看
上圖中可以發(fā)現(xiàn),倒數(shù)第三位也就是tall值為1翁巍,倒數(shù)第二位也就是rich值為0驴一,倒數(shù)一位也就是handsome值為1,如此看來(lái)和上述代碼中我們?cè)O(shè)置的值一樣灶壶「味希可以成功賦值。
接著繼續(xù)打印內(nèi)容:
Runtime - union探尋[59366:4053478] tall : -1, rich : 0, handsome : -1
此時(shí)可以發(fā)現(xiàn)問(wèn)題驰凛,tall與handsome我們?cè)O(shè)值為YES胸懈,講道理應(yīng)該輸出的值為1為何上面輸出為-1呢?
并且上面通過(guò)打印_tallRichHandsome
中存儲(chǔ)的值恰响,也確認(rèn)tall
和handsome
的值都為1趣钱。我們?cè)俅未蛴?code>_tallRichHandsome結(jié)構(gòu)體內(nèi)變量的值。
上圖中可以發(fā)現(xiàn)胚宦,handsome的值為0x01首有,通過(guò)計(jì)算器將其轉(zhuǎn)化為二進(jìn)制
可以看到值確實(shí)為1的,為什么打印出來(lái)值為-1呢枢劝?此時(shí)應(yīng)該可以想到應(yīng)該是get方法內(nèi)部有問(wèn)題井联。我們來(lái)到get方法內(nèi)部通過(guò)打印斷點(diǎn)查看獲取到的值。
- (BOOL)handsome
{
BOOL ret = _tallRichHandsome.handsome;
return ret;
}
打印ret的值
通過(guò)打印ret的值發(fā)現(xiàn)其值為255您旁,也就是1111 1111
烙常,此時(shí)也就能解釋為什么打印出來(lái)值為 -1了,首先此時(shí)通過(guò)結(jié)構(gòu)體獲取到的handsome
的值為0b1
只占一個(gè)內(nèi)存空間中的1位鹤盒,但是BOOL值占據(jù)一個(gè)內(nèi)存空間蚕脏,也就是8位。當(dāng)僅有1位的值擴(kuò)展成8位的話(huà)侦锯,其余空位就會(huì)根據(jù)前面一位的值全部補(bǔ)位成1驼鞭,因此此時(shí)ret的值就被映射成了0b 11111 1111
。
11111111
在一個(gè)字節(jié)時(shí)尺碰,有符號(hào)數(shù)則為-1终议,無(wú)符號(hào)數(shù)則為255。因此我們?cè)诖蛴r(shí)候打印出的值為-1
為了驗(yàn)證當(dāng)1位的值擴(kuò)展成8位時(shí)葱蝗,會(huì)全部補(bǔ)位穴张,我們將tall、rich两曼、handsome值設(shè)置為占據(jù)兩位皂甘。
@interface Person()
{
struct {
char tall : 2;
char rich : 2;
char handsome : 2;
}_tallRichHandsome;
}
此時(shí)在打印就發(fā)現(xiàn)值可以正常打印出來(lái)。
Runtime - union探尋[60827:4259630] tall : 1, rich : 0, handsome : 1
這是因?yàn)榈看眨趃et方法內(nèi)部獲取到的_tallRichHandsome.handsome
為兩位的也就是0b 01
偿枕,此時(shí)在賦值給8位的BOOL類(lèi)型的值時(shí)璧瞬,前面的空值就會(huì)自動(dòng)根據(jù)前面一位補(bǔ)全為0,因此返回的值為0b 0000 0001
渐夸,因此打印出的值也就為1了嗤锉。
因此上述問(wèn)題同樣可以使用!!
雙感嘆號(hào)來(lái)解決問(wèn)題。!!
的原理上面已經(jīng)講解過(guò)墓塌,這里不再贅述了瘟忱。
使用結(jié)構(gòu)體位域優(yōu)化之后的代碼
@interface Person()
{
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
}_tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)tall
{
return !!_tallRichHandsome.tall;
}
- (BOOL)rich
{
return !!_tallRichHandsome.rich;
}
- (BOOL)handsome
{
return !!_tallRichHandsome.handsome;
}
上述代碼中使用結(jié)構(gòu)體的位域則不在需要使用掩碼,使代碼可讀性增強(qiáng)了很多苫幢,但是效率相比直接使用位運(yùn)算的方式來(lái)說(shuō)差很多访诱,如果想要高效率的進(jìn)行數(shù)據(jù)的讀取與存儲(chǔ)同時(shí)又有較強(qiáng)的可讀性就需要使用到共用體了。
共用體
為了使代碼存儲(chǔ)數(shù)據(jù)高效率的同時(shí)韩肝,有較強(qiáng)的可讀性触菜,可以使用共用體來(lái)增強(qiáng)代碼可讀性,同時(shí)使用位運(yùn)算來(lái)提高數(shù)據(jù)存取的效率哀峻。
使用共用體優(yōu)化的代碼
#define TallMask (1<<2) // 0b00000100 4
#define RichMask (1<<1) // 0b00000010 2
#define HandsomeMask (1<<0) // 0b00000001 1
@interface Person()
{
union {
char bits;
// 結(jié)構(gòu)體僅僅是為了增強(qiáng)代碼可讀性涡相,無(wú)實(shí)質(zhì)用處
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
}_tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= TallMask;
}else{
_tallRichHandsome.bits &= ~TallMask;
}
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= RichMask;
}else{
_tallRichHandsome.bits &= ~RichMask;
}
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= HandsomeMask;
}else{
_tallRichHandsome.bits &= ~HandsomeMask;
}
}
- (BOOL)tall
{
return !!(_tallRichHandsome.bits & TallMask);
}
- (BOOL)rich
{
return !!(_tallRichHandsome.bits & RichMask);
}
- (BOOL)handsome
{
return !!(_tallRichHandsome.bits & HandsomeMask);
}
上述代碼中使用位運(yùn)算這種比較高效的方式存取值,使用union共用體來(lái)對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ)剩蟀。增加讀取效率的同時(shí)增強(qiáng)代碼可讀性漾峡。
其中_tallRichHandsome
共用體只占用一個(gè)字節(jié),因?yàn)榻Y(jié)構(gòu)體中tall喻旷、rich、handsome都只占一位二進(jìn)制空間牢屋,所以結(jié)構(gòu)體只占一個(gè)字節(jié)且预,而char類(lèi)型的bits也只占一個(gè)字節(jié),他們都在共用體中烙无,因此共用一個(gè)字節(jié)的內(nèi)存即可锋谐。
并且在get、set
方法中并沒(méi)有使用到結(jié)構(gòu)體截酷,結(jié)構(gòu)體僅僅為了增加代碼可讀性涮拗,指明共用體中存儲(chǔ)了哪些值,以及這些值各占多少位空間迂苛。同時(shí)存值取值還使用位運(yùn)算來(lái)增加效率三热,存儲(chǔ)使用共用體,存放的位置依然通過(guò)與掩碼進(jìn)行位運(yùn)算來(lái)控制三幻。
此時(shí)代碼已經(jīng)算是優(yōu)化完成了就漾,高效的同時(shí)可讀性高,那么此時(shí)在回頭看isa_t
共用體的源碼
isa_t源碼
此時(shí)我們?cè)诨仡^查看isa_t源碼
// 精簡(jiǎn)過(guò)的isa_t共用體
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# 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;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
#endif
};
經(jīng)過(guò)上面對(duì)位運(yùn)算念搬、位域以及共用體的分析抑堡,現(xiàn)在再來(lái)看源碼已經(jīng)可以很清晰的理解其中的內(nèi)容摆出。源碼中通過(guò)共用體的形式存儲(chǔ)了64位的值,這些值在結(jié)構(gòu)體中被展示出來(lái)首妖,通過(guò)對(duì)bits
進(jìn)行位運(yùn)算而取出相應(yīng)位置的值偎漫。
這里主要關(guān)注一下shiftcls
,shiftcls
中存儲(chǔ)著Class有缆、Meta-Class
對(duì)象的內(nèi)存地址信息象踊,我們之前在OC對(duì)象的本質(zhì)中提到過(guò),對(duì)象的isa指針需要同ISA_MASK
經(jīng)過(guò)一次&(按位與)運(yùn)算才能得出真正的Class對(duì)象地址妒貌。
那么此時(shí)我們重新來(lái)看ISA_MASK
的值0x0000000ffffffff8ULL
通危,我們將其轉(zhuǎn)化為二進(jìn)制數(shù)
上圖中可以看出ISA_MASK
的值轉(zhuǎn)化為二進(jìn)制中有33位都為1,上面提到過(guò)按位與的作用是可以取出這33位中的值灌曙。那么此時(shí)很明顯了菊碟,同ISA_MASK
進(jìn)行按位與運(yùn)算即可以取出Class或Meta-Class的值。
同時(shí)可以看出ISA_MASK
最后三位的值為0在刺,那么任何數(shù)同ISA_MASK
按位與運(yùn)算之后逆害,得到的最后三位必定都為0,因此任何類(lèi)對(duì)象或元類(lèi)對(duì)象的內(nèi)存地址最后三位必定為0蚣驼,轉(zhuǎn)化為十六進(jìn)制末位必定為8或者0魄幕。
isa中存儲(chǔ)的信息及作用
將結(jié)構(gòu)體取出來(lái)標(biāo)記一下這些信息的作用。
struct {
// 0代表普通的指針颖杏,存儲(chǔ)著Class纯陨,Meta-Class對(duì)象的內(nèi)存地址。
// 1代表優(yōu)化后的使用位域存儲(chǔ)更多的信息留储。
uintptr_t nonpointer : 1;
// 是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象翼抠,如果沒(méi)有,釋放時(shí)會(huì)更快
uintptr_t has_assoc : 1;
// 是否有C++析構(gòu)函數(shù)获讳,如果沒(méi)有阴颖,釋放時(shí)會(huì)更快
uintptr_t has_cxx_dtor : 1;
// 存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
uintptr_t shiftcls : 33;
// 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
uintptr_t magic : 6;
// 是否有被弱引用指向過(guò)丐膝。
uintptr_t weakly_referenced : 1;
// 對(duì)象是否正在釋放
uintptr_t deallocating : 1;
// 引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中
// 如果為1量愧,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類(lèi)的屬性中
uintptr_t has_sidetable_rc : 1;
// 里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
uintptr_t extra_rc : 19;
};
驗(yàn)證
通過(guò)下面一段代碼驗(yàn)證上述信息存儲(chǔ)的位置及作用
// 以下代碼需要在真機(jī)中運(yùn)行,因?yàn)檎鏅C(jī)中才是__arm64__ 位架構(gòu)
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSLog(@"%p",[person class]);
NSLog(@"%@",person);
}
首先打印person類(lèi)對(duì)象的地址帅矗,之后通過(guò)斷點(diǎn)打印一下person對(duì)象的isa指針地址偎肃。
首先來(lái)看一下打印的內(nèi)容
將類(lèi)對(duì)象地址轉(zhuǎn)化為二進(jìn)制
將person的isa指針地址轉(zhuǎn)化為二進(jìn)制
shiftcls : shiftcls
中存儲(chǔ)類(lèi)對(duì)象地址,通過(guò)上面兩張圖對(duì)比可以發(fā)現(xiàn)存儲(chǔ)類(lèi)對(duì)象地址的33位二進(jìn)制內(nèi)容完全相同浑此。
extra_rc : extra_rc
的19位中存儲(chǔ)著的值為引用計(jì)數(shù)減一软棺,因?yàn)榇藭r(shí)person的引用計(jì)數(shù)為1,因此此時(shí)extra_rc
的19位二進(jìn)制中存儲(chǔ)的是0尤勋。
magic : magic
的6位用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化喘落,上述代碼中person已經(jīng)完成初始化茵宪,那么此時(shí)這6位二進(jìn)制中存儲(chǔ)的值011010
即為共用體中定義的宏# define ISA_MAGIC_VALUE 0x000001a000000001ULL
的值。
nonpointer : 這里肯定是使用的優(yōu)化后的isa瘦棋,因此nonpointer
的值肯定為1
因?yàn)榇藭r(shí)person對(duì)象沒(méi)有關(guān)聯(lián)對(duì)象并且沒(méi)有弱指針引用過(guò)稀火,可以看出has_assoc
和weakly_referenced
值都為0,接著我們?yōu)閜erson對(duì)象添加弱引用和關(guān)聯(lián)對(duì)象赌朋,來(lái)觀(guān)察一下has_assoc
和weakly_referenced
的變化凰狞。
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSLog(@"%p",[person class]);
// 為person添加弱引用
__weak Person *weakPerson = person;
// 為person添加關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(person, @"name", @"xx_cc", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"%@",person);
}
重新打印person的isa指針地址將其轉(zhuǎn)化為二進(jìn)制可以看到has_assoc
和weakly_referenced
的值都變成了1
注意:只要設(shè)置過(guò)關(guān)聯(lián)對(duì)象或者弱引用引用過(guò)對(duì)象has_assoc
和weakly_referenced
的值就會(huì)變成1,不論之后是否將關(guān)聯(lián)對(duì)象置為nil或斷開(kāi)弱引用沛慢。
如果沒(méi)有設(shè)置過(guò)關(guān)聯(lián)對(duì)象赡若,對(duì)象釋放時(shí)會(huì)更快,這是因?yàn)閷?duì)象在銷(xiāo)毀時(shí)會(huì)判斷是否有關(guān)聯(lián)對(duì)象進(jìn)而對(duì)關(guān)聯(lián)對(duì)象釋放团甲。來(lái)看一下對(duì)象銷(xiāo)毀的源碼
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa = obj->getIsa();
// 是否有c++析構(gòu)函數(shù)
if (isa->hasCxxDtor()) {
object_cxxDestruct(obj);
}
// 是否有關(guān)聯(lián)對(duì)象逾冬,如果有則移除
if (isa->instancesHaveAssociatedObjects()) {
_object_remove_assocations(obj);
}
objc_clear_deallocating(obj);
}
return obj;
}
相信至此我們已經(jīng)對(duì)isa指針有了新的認(rèn)識(shí),__arm64__
架構(gòu)之后躺苦,isa指針不單單只存儲(chǔ)了Class或Meta-Class的地址身腻,而是使用共用體的方式存儲(chǔ)了更多信息,其中shiftcls
存儲(chǔ)了Class或Meta-Class的地址匹厘,需要同ISA_MASK
進(jìn)行按位&運(yùn)算才可以取出其內(nèi)存地址值嘀趟。
底層原理相關(guān)文章:
文中如果有不對(duì)的地方歡迎指出。我是xx_cc愈诚,一只長(zhǎng)大很久但還沒(méi)有二夠的家伙她按。