iOS底層原理總結(jié) - 探尋Runtime本質(zhì)(一)

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的值。如下圖所示

存儲(chǔ)方式

那么現(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):

  1. 如果一個(gè)字節(jié)所拭谛澹空間不夠存放另一位域時(shí)钮追,應(yīng)從下一單元起存放該位域。也可以有意使某位域從下一單元開(kāi)始阿迈。
  2. 位域的長(zhǎng)度不能大于數(shù)據(jù)類(lèi)型本身的長(zhǎng)度元媚,比如int類(lèi)型就不能超過(guò)32位二進(jìn)位。
  3. 位域可以無(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ǔ)的值

_tallRichHandsome內(nèi)存儲(chǔ)的值

因?yàn)?code>_tallRichHandsome占據(jù)一個(gè)內(nèi)存空間嗤无,也就是8個(gè)二進(jìn)制位震束,我們將05十六進(jìn)制轉(zhuǎn)化為二進(jìn)制查看

05轉(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)tallhandsome的值都為1趣钱。我們?cè)俅未蛴?code>_tallRichHandsome結(jié)構(gòu)體內(nèi)變量的值。

person內(nèi)部_tallRichHandsome結(jié)構(gòu)體變量

上圖中可以發(fā)現(xiàn)胚宦,handsome的值為0x01首有,通過(guò)計(jì)算器將其轉(zhuǎn)化為二進(jìn)制

0x01二進(jìn)制數(shù)

可以看到值確實(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的值

po 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)注一下shiftclsshiftcls中存儲(chǔ)著Class有缆、Meta-Class對(duì)象的內(nèi)存地址信息象踊,我們之前在OC對(duì)象的本質(zhì)中提到過(guò),對(duì)象的isa指針需要同ISA_MASK經(jīng)過(guò)一次&(按位與)運(yùn)算才能得出真正的Class對(duì)象地址妒貌。

isa指針按位與得到Class對(duì)象地址

那么此時(shí)我們重新來(lái)看ISA_MASK的值0x0000000ffffffff8ULL通危,我們將其轉(zhuǎn)化為二進(jìn)制數(shù)

0x0000000ffffffff8ULL二進(jìn)制

上圖中可以看出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)容

打印內(nèi)容

將類(lèi)對(duì)象地址轉(zhuǎn)化為二進(jìn)制

類(lèi)對(duì)象地址

將person的isa指針地址轉(zhuǎn)化為二進(jìn)制

person對(duì)象的isa指針地址

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_assocweakly_referenced值都為0,接著我們?yōu)閜erson對(duì)象添加弱引用和關(guān)聯(lián)對(duì)象赌朋,來(lái)觀(guān)察一下has_assocweakly_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_assocweakly_referenced的值都變成了1

has_assoc和weakly_referenced的變化

注意:只要設(shè)置過(guò)關(guān)聯(lián)對(duì)象或者弱引用引用過(guò)對(duì)象has_assocweakly_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)文章:

iOS底層原理總結(jié) - 文集


文中如果有不對(duì)的地方歡迎指出。我是xx_cc愈诚,一只長(zhǎng)大很久但還沒(méi)有二夠的家伙她按。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市炕柔,隨后出現(xiàn)的幾起案子酌泰,更是在濱河造成了極大的恐慌,老刑警劉巖汗唱,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丈攒,居然都是意外死亡哩罪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)巡验,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)际插,“玉大人,你說(shuō)我怎么就攤上這事显设】虺冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵捕捂,是天一觀(guān)的道長(zhǎng)瑟枫。 經(jīng)常有香客問(wèn)我斗搞,道長(zhǎng),這世上最難降的妖魔是什么慷妙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任僻焚,我火速辦了婚禮,結(jié)果婚禮上膝擂,老公的妹妹穿的比我還像新娘虑啤。我一直安慰自己,他們只是感情好架馋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布狞山。 她就那樣靜靜地躺著,像睡著了一般叉寂。 火紅的嫁衣襯著肌膚如雪萍启。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天办绝,我揣著相機(jī)與錄音伊约,去河邊找鬼。 笑死孕蝉,一個(gè)胖子當(dāng)著我的面吹牛屡律,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播降淮,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼超埋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了佳鳖?” 一聲冷哼從身側(cè)響起霍殴,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎系吩,沒(méi)想到半個(gè)月后来庭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穿挨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年月弛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片科盛。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帽衙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贞绵,到底是詐尸還是另有隱情厉萝,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站谴垫,受9級(jí)特大地震影響章母,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弹渔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一胳施、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肢专,春花似錦舞肆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至剃根,卻和暖如春哩盲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狈醉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工廉油, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苗傅。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓抒线,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親渣慕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘶炭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容