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

isa的本質(zhì)

在學(xué)習(xí)Runtime之前首先需要對isa的本質(zhì)有一定的了解斋日,這樣之后學(xué)習(xí)Runtime會更便于理解型凳。

回顧OC對象的本質(zhì),每個OC對象都含有一個isa指針碍拆,__arm64__之前逸寓,isa僅僅是一個指針居兆,保存著對象或類對象內(nèi)存地址,在__arm64__架構(gòu)之后竹伸,apple對isa進(jìn)行了優(yōu)化泥栖,變成了一個共用體(union)結(jié)構(gòu),同時使用位域來存儲更多的信息。

我們知道OC對象的isa指針并不是直接指向類對象或者元類對象吧享,而是需要&ISA_MASK通過位運(yùn)算才能獲取到類對象或者元類對象的地址魏割。今天來探尋一下為什么需要&ISA_MASK才能獲取到類對象或者元類對象的地址,以及這樣的好處钢颂。

首先在源碼中找到isa指針钞它,看一下isa指針的本質(zhì)。

// 截取objc_object內(nèi)部分代碼
struct objc_object {
private:
    isa_t isa;
}

isa指針其實是一個isa_t類型的共用體甸陌,來到isa_t內(nèi)部查看其結(jié)構(gòu)

// 精簡過的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_tunion類型,union表示共用體盐股∏恚可以看到共用體中有一個結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部分別定義了一些變量疯汁,變量后面的值代表的是該變量占用多少個字節(jié)牲尺,也就是位域技術(shù)。

共用體:在進(jìn)行某些算法的C語言編程的時候幌蚊,需要使幾種不同類型的變量存放到同一段內(nèi)存單元中谤碳。也就是使用覆蓋技術(shù),幾個變量互相覆蓋溢豆。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu)蜒简,在C語言中,被稱作“共用體”類型結(jié)構(gòu)漩仙,簡稱共用體搓茬。

接下來使用共用體的方式來深入的了解apple為什么要使用共用體,以及使用共用體的好處队他。

探尋過程

接下來使用代碼來模仿底層的做法卷仑,創(chuàng)建一個person類并含有三個BOOL類型的成員變量。

@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個BOOL類型的屬性麸折,打印Person類對象占據(jù)內(nèi)存空間為16锡凝,也就是(isa指針 = 8) + (BOOL tall = 1) + (BOOL rich = 1) + (BOOL handsome = 1) = 13。因為內(nèi)存對齊原則所以Person類對象占據(jù)內(nèi)存空間為16垢啼。

上面提到過共用體中變量可以相互覆蓋窜锯,可以使幾個不同的變量存放到同一段內(nèi)存單元中,可以很大程度上節(jié)省內(nèi)存空間芭析。

那么我們知道BOOL值只有兩種情況 0 或者 1衬浑,但是卻占據(jù)了一個字節(jié)的內(nèi)存空間,而一個內(nèi)存空間中有8個二進(jìn)制位放刨,并且二進(jìn)制只有 0 或者 1 工秩。那么是否可以使用1個二進(jìn)制位來表示一個BOOL值,也就是說3個BOOL值最終只使用3個二進(jìn)制位,也就是一個內(nèi)存空間即可呢助币?如何實現(xiàn)這種方式浪听?

首先如果使用這種方式需要自己寫方法聲明與實現(xiàn),不可以寫屬性眉菱,因為一旦寫屬性迹栓,系統(tǒng)會自動幫我們添加成員變量。

另外想要將三個BOOL值存放在一個字節(jié)中俭缓,我們可以添加一個char類型的成員變量克伊,char類型占據(jù)一個字節(jié)內(nèi)存空間,也就是8個二進(jìn)制位华坦≡复担可以使用其中最后三個二進(jìn)制位來存儲3個BOOL值。

@interface Person()
{
    char _tallRichHandsome;
}

例如_tallRichHansome的值為 0b 0000 0010 惜姐,那么只使用8個二進(jìn)制位中的最后3個犁跪,分別為其賦值0或者1來代表tall、rich歹袁、handsome的值坷衍。如下圖所示

image.png

那么現(xiàn)在面臨的問題就是如何取出8個二進(jìn)制位中的某一位的值,或者為某一位賦值呢条舔?

取值

首先來看一下取值枫耳,假如char類型的成員變量中存儲的二進(jìn)制為0b 0000 0010如果想將倒數(shù)第2位的值也就是rich的值取出來,可以使用&進(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

按位與可以用來取出特定的位帆喇,想取出哪一位就將那一位置為1警医,其他為都置為0,然后同原數(shù)據(jù)進(jìn)行按位與計算坯钦,即可取出特定的位预皇。

那么此時可以將get方法寫成如下方式

#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);
}

上述代碼中使用兩個!!(非)來將值改為bool類型。同樣使用上面的例子

// 取出倒數(shù)第二位 rich
  0000 0010  // _tallRichHandsome
& 0000 0010 // RichMask
------------
  0000 0010 // 取出rich的值為1婉刀,其他位都置為0

上述代碼中(_tallRichHandsome & TallMask)的值為0000 0010也就是2吟温,但是我們需要的是一個BOOL類型的值 0 或者 1 ,那么!!2就將 2 先轉(zhuǎn)化為 0 突颊,之后又轉(zhuǎn)化為 1鲁豪。相反如果按位與取得的值為 0 時潘悼,!!0將 0 先轉(zhuǎn)化為 1 之后又轉(zhuǎn)化為 0。 因此使用!!兩個非操作將值轉(zhuǎn)化為 0 或者 1 來表示相應(yīng)的值爬橡。

掩碼 : 上述代碼中定義了三個宏治唤,用來分別進(jìn)行按位與運(yùn)算而取出相應(yīng)的值,一般用來按位與(&)運(yùn)算的值稱之為掩碼糙申。

為了能更清晰的表明掩碼是為了取出哪一位的值宾添,上述三個宏的定義可以使用<<(左移)來優(yōu)化

<<:表示左移一位,下圖為例柜裸。

image.png

那么上述宏定義可以使用<<(左移)優(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缕陕,可以使用|(按位或)操作符。 | : 按位或疙挺,只要有一個1即為1扛邑,否則為0。

如果想將某一位置為1的話衔统,那么將原本的值與掩碼進(jìn)行按位或的操作即可鹿榜,例如我們想將tall置為1

// 將倒數(shù)第三位 tall置為1
  0000 0010  // _tallRichHandsome
| 0000 0100  // TallMask
------------
  0000 0110 // 將tall置為1海雪,其他位值都不變

如果想將某一位置為0的話锦爵,需要將掩碼按位取反(~ : 按位取反符),之后在與原本的值進(jìn)行按位與操作即可奥裸。

// 將倒數(shù)第二位 rich置為0
  0000 0010  // _tallRichHandsome
& 1111 1101  // RichMask按位取反
------------
  0000 0000 // 將rich置為0险掀,其他位值都不變

此時set方法內(nèi)部實現(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;
    }
}

寫完set、get方法之后通過代碼來查看一下是否可以設(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)需要添加新屬性的時候埠啃,需要重復(fù)上述工作,并且代碼可讀性比較差伟恶。接下來使用結(jié)構(gòu)體的位域特性來優(yōu)化上述代碼碴开。

位域

將上述代碼進(jìn)行優(yōu)化,使用結(jié)構(gòu)體位域博秫,可以使代碼可讀性更高潦牛。 位域聲明 位域名 : 位域長度;

使用位域需要注意以下3點: 1. 如果一個字節(jié)所剩空間不夠存放另一位域時挡育,應(yīng)從下一單元起存放該位域巴碗。也可以有意使某位域從下一單元開始。 2. 位域的長度不能大于數(shù)據(jù)類型本身的長度即寒,比如int類型就不能超過32位二進(jìn)位橡淆。 3. 位域可以無位域名召噩,這時它只用來作填充或調(diào)整位置。無名的位域是不能使用的明垢。

上述代碼使用結(jié)構(gòu)體位域優(yōu)化之后蚣常。

@interface Person()
{
    struct {
        char handsome : 1; // 位域,代表占用一位空間
        char rich : 1;  // 按照順序只占一位空間
        char tall : 1; 
    }_tallRichHandsome;
}

set痊银、get方法中可以直接通過結(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;
}

通過代碼驗證一下是否可以賦值或取值正確

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處打個斷點抵蚊,查看_tallRichHandsome內(nèi)存儲的值

image.png

因為_tallRichHandsome占據(jù)一個內(nèi)存空間,也就是8個二進(jìn)制位溯革,我們將05十六進(jìn)制轉(zhuǎn)化為二進(jìn)制查看

image.png

上圖中可以發(fā)現(xiàn)贞绳,倒數(shù)第三位也就是tall值為1,倒數(shù)第二位也就是rich值為0致稀,倒數(shù)一位也就是handsome值為1冈闭,如此看來和上述代碼中我們設(shè)置的值一樣《兜ィ可以成功賦值萎攒。

接著繼續(xù)打印內(nèi)容: Runtime - union探尋[59366:4053478] tall : -1, rich : 0, handsome : -1

此時可以發(fā)現(xiàn)問題,tall與handsome我們設(shè)值為YES矛绘,講道理應(yīng)該輸出的值為1為何上面輸出為-1呢耍休?

并且上面通過打印_tallRichHandsome中存儲的值,也確認(rèn)tallhandsome的值都為1货矮。我們再次打印_tallRichHandsome結(jié)構(gòu)體內(nèi)變量的值羊精。

image.png

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

image.png

可以看到值確實為1的喧锦,為什么打印出來值為-1呢?此時應(yīng)該可以想到應(yīng)該是get方法內(nèi)部有問題抓督。我們來到get方法內(nèi)部通過打印斷點查看獲取到的值燃少。

- (BOOL)handsome
{
    BOOL ret = _tallRichHandsome.handsome;
    return ret;
}

打印ret的值

image.png

通過打印ret的值發(fā)現(xiàn)其值為255,也就是1111 1111铃在,此時也就能解釋為什么打印出來值為 -1了阵具,首先此時通過結(jié)構(gòu)體獲取到的handsome的值為0b1只占一個內(nèi)存空間中的1位,但是BOOL值占據(jù)一個內(nèi)存空間涌穆,也就是8位怔昨。當(dāng)僅有1位的值擴(kuò)展成8位的話,其余空位就會根據(jù)前面一位的值全部補(bǔ)位成1宿稀,因此此時ret的值就被映射成了0b 11111 1111趁舀。

11111111 在一個字節(jié)時,有符號數(shù)則為-1祝沸,無符號數(shù)則為255矮烹。因此我們在打印時候打印出的值為-1

為了驗證當(dāng)1位的值擴(kuò)展成8位時越庇,會全部補(bǔ)位,我們將tall奉狈、rich卤唉、handsome值設(shè)置為占據(jù)兩位。

@interface Person()
{
    struct {
        char tall : 2;
        char rich : 2;
        char handsome : 2;
    }_tallRichHandsome;
}

此時在打印就發(fā)現(xiàn)值可以正常打印出來仁期。 Runtime - union探尋[60827:4259630] tall : 1, rich : 0, handsome : 1

這是因為桑驱,在get方法內(nèi)部獲取到的_tallRichHandsome.handsome為兩位的也就是0b 01,此時在賦值給8位的BOOL類型的值時跛蛋,前面的空值就會自動根據(jù)前面一位補(bǔ)全為0熬的,因此返回的值為0b 0000 0001,因此打印出的值也就為1了赊级。

因此上述問題同樣可以使用!!雙感嘆號來解決問題押框。!!的原理上面已經(jīng)講解過,這里不再贅述了理逊。

使用結(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)算的方式來說差很多晋被,如果想要高效率的進(jìn)行數(shù)據(jù)的讀取與存儲同時又有較強(qiáng)的可讀性就需要使用到共用體了兑徘。

共用體

為了使代碼存儲數(shù)據(jù)高效率的同時,有較強(qiáng)的可讀性墨微,可以使用共用體來增強(qiáng)代碼可讀性道媚,同時使用位運(yùn)算來提高數(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)代碼可讀性翘县,無實質(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共用體來對數(shù)據(jù)進(jìn)行存儲谴分。增加讀取效率的同時增強(qiáng)代碼可讀性锈麸。

其中_tallRichHandsome共用體只占用一個字節(jié),因為結(jié)構(gòu)體中tall牺蹄、rich忘伞、handsome都只占一位二進(jìn)制空間,所以結(jié)構(gòu)體只占一個字節(jié)沙兰,而char類型的bits也只占一個字節(jié)氓奈,他們都在共用體中,因此共用一個字節(jié)的內(nèi)存即可鼎天。

并且在get舀奶、set方法中并沒有使用到結(jié)構(gòu)體,結(jié)構(gòu)體僅僅為了增加代碼可讀性斋射,指明共用體中存儲了哪些值育勺,以及這些值各占多少位空間但荤。同時存值取值還使用位運(yùn)算來增加效率,存儲使用共用體涧至,存放的位置依然通過與掩碼進(jìn)行位運(yùn)算來控制腹躁。

此時代碼已經(jīng)算是優(yōu)化完成了,高效的同時可讀性高南蓬,那么此時在回頭看isa_t共用體的源碼

isa_t源碼

此時我們在回頭查看isa_t源碼

// 精簡過的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)過上面對位運(yùn)算纺非、位域以及共用體的分析,現(xiàn)在再來看源碼已經(jīng)可以很清晰的理解其中的內(nèi)容赘方。源碼中通過共用體的形式存儲了64位的值铐炫,這些值在結(jié)構(gòu)體中被展示出來,通過對bits進(jìn)行位運(yùn)算而取出相應(yīng)位置的值蒜焊。

這里主要關(guān)注一下shiftcls倒信,shiftcls中存儲著Class、Meta-Class對象的內(nèi)存地址信息泳梆,我們之前在OC對象的本質(zhì)中提到過鳖悠,對象的isa指針需要同ISA_MASK經(jīng)過一次&(按位與)運(yùn)算才能得出真正的Class對象地址。

image.png

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

image.png

上圖中可以看出ISA_MASK的值轉(zhuǎn)化為二進(jìn)制中有33位都為1乘综,上面提到過按位與的作用是可以取出這33位中的值。那么此時很明顯了套硼,同ISA_MASK進(jìn)行按位與運(yùn)算即可以取出Class或Meta-Class的值卡辰。

同時可以看出ISA_MASK最后三位的值為0,那么任何數(shù)同ISA_MASK按位與運(yùn)算之后邪意,得到的最后三位必定都為0九妈,因此任何類對象或元類對象的內(nèi)存地址最后三位必定為0,轉(zhuǎn)化為十六進(jìn)制末位必定為8或者0雾鬼。

isa中存儲的信息及作用

將結(jié)構(gòu)體取出來標(biāo)記一下這些信息的作用萌朱。

struct {
    // 0代表普通的指針,存儲著Class策菜,Meta-Class對象的內(nèi)存地址晶疼。
    // 1代表優(yōu)化后的使用位域存儲更多的信息。
    uintptr_t nonpointer        : 1; 

   // 是否有設(shè)置過關(guān)聯(lián)對象又憨,如果沒有翠霍,釋放時會更快
    uintptr_t has_assoc         : 1;

    // 是否有C++析構(gòu)函數(shù),如果沒有蠢莺,釋放時會更快
    uintptr_t has_cxx_dtor      : 1;

    // 存儲著Class寒匙、Meta-Class對象的內(nèi)存地址信息
    uintptr_t shiftcls          : 33; 

    // 用于在調(diào)試時分辨對象是否未完成初始化
    uintptr_t magic             : 6;

    // 是否有被弱引用指向過。
    uintptr_t weakly_referenced : 1;

    // 對象是否正在釋放
    uintptr_t deallocating      : 1;

    // 引用計數(shù)器是否過大無法存儲在isa中
    // 如果為1浪秘,那么引用計數(shù)會存儲在一個叫SideTable的類的屬性中
    uintptr_t has_sidetable_rc  : 1;

    // 里面存儲的值是引用計數(shù)器減1
    uintptr_t extra_rc          : 19;
};

驗證

通過下面一段代碼驗證上述信息存儲的位置及作用

// 以下代碼需要在真機(jī)中運(yùn)行蒋情,因為真機(jī)中才是__arm64__ 位架構(gòu)
- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    NSLog(@"%p",[person class]);
    NSLog(@"%@",person);
}

首先打印person類對象的地址埠况,之后通過斷點打印一下person對象的isa指針地址。

首先來看一下打印的內(nèi)容

image.png

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

image.png

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

image.png

shiftcls : shiftcls中存儲類對象地址棵癣,通過上面兩張圖對比可以發(fā)現(xiàn)存儲類對象地址的33位二進(jìn)制內(nèi)容完全相同辕翰。

extra_rc : extra_rc的19位中存儲著的值為引用計數(shù)減一,因為此時person的引用計數(shù)為1狈谊,因此此時extra_rc的19位二進(jìn)制中存儲的是0喜命。

magic : magic的6位用于在調(diào)試時分辨對象是否未完成初始化,上述代碼中person已經(jīng)完成初始化河劝,那么此時這6位二進(jìn)制中存儲的值011010即為共用體中定義的宏# define ISA_MAGIC_VALUE 0x000001a000000001ULL的值壁榕。

nonpointer : 這里肯定是使用的優(yōu)化后的isa,因此nonpointer的值肯定為1

因為此時person對象沒有關(guān)聯(lián)對象并且沒有弱指針引用過赎瞎,可以看出has_assocweakly_referenced值都為0牌里,接著我們?yōu)閜erson對象添加弱引用和關(guān)聯(liá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)對象
    objc_setAssociatedObject(person, @"name", @"xx_cc", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSLog(@"%@",person);
}

重新打印person的isa指針地址將其轉(zhuǎn)化為二進(jìn)制可以看到has_assocweakly_referenced的值都變成了1

image.png

注意:只要設(shè)置過關(guān)聯(lián)對象或者弱引用引用過對象has_assocweakly_referenced的值就會變成1牡辽,不論之后是否將關(guān)聯(lián)對象置為nil或斷開弱引用。

如果沒有設(shè)置過關(guān)聯(lián)對象敞临,對象釋放時會更快态辛,這是因為對象在銷毀時會判斷是否有關(guān)聯(lián)對象進(jìn)而對關(guān)聯(lián)對象釋放。來看一下對象銷毀的源碼

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)對象挺尿,如果有則移除
        if (isa->instancesHaveAssociatedObjects()) {
            _object_remove_assocations(obj);
        }
        objc_clear_deallocating(obj);
    }
    return obj;
}

相信至此我們已經(jīng)對isa指針有了新的認(rèn)識奏黑,__arm64__架構(gòu)之后,isa指針不單單只存儲了Class或Meta-Class的地址编矾,而是使用共用體的方式存儲了更多信息熟史,其中shiftcls存儲了Class或Meta-Class的地址,需要同ISA_MASK進(jìn)行按位&運(yùn)算才可以取出其內(nèi)存地址值洽沟。

Runtime 博文推薦

Runtime圖解
Runtime 10種實際用法
完整總結(jié)
objc_msgSend
詳解
快速上手
消息機(jī)制
Method Swizzling開發(fā)實例匯總
最實用的runtime總結(jié)
實際開發(fā)中的應(yīng)用

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

參考文章:
https://juejin.im/post/5b238de251882574b409451e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末以故,一起剝皮案震驚了整個濱河市蜗细,隨后出現(xiàn)的幾起案子裆操,更是在濱河造成了極大的恐慌,老刑警劉巖炉媒,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踪区,死亡現(xiàn)場離奇詭異,居然都是意外死亡吊骤,警方通過查閱死者的電腦和手機(jī)缎岗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來白粉,“玉大人传泊,你說我怎么就攤上這事鼠渺。” “怎么了眷细?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵拦盹,是天一觀的道長。 經(jīng)常有香客問我溪椎,道長普舆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任校读,我火速辦了婚禮沼侣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歉秫。我一直安慰自己蛾洛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布雁芙。 她就那樣靜靜地躺著雅潭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪却特。 梳的紋絲不亂的頭發(fā)上扶供,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音裂明,去河邊找鬼椿浓。 笑死,一個胖子當(dāng)著我的面吹牛闽晦,可吹牛的內(nèi)容都是我干的扳碍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仙蛉,長吁一口氣:“原來是場噩夢啊……” “哼笋敞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荠瘪,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤夯巷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哀墓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁餐,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年篮绰,在試婚紗的時候發(fā)現(xiàn)自己被綠了后雷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖臀突,靈堂內(nèi)的尸體忽然破棺而出勉抓,到底是詐尸還是另有隱情,我是刑警寧澤候学,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布琳状,位于F島的核電站,受9級特大地震影響盒齿,放射性物質(zhì)發(fā)生泄漏念逞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一边翁、第九天 我趴在偏房一處隱蔽的房頂上張望翎承。 院中可真熱鬧,春花似錦符匾、人聲如沸叨咖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甸各。三九已至,卻和暖如春焰坪,著一層夾襖步出監(jiān)牢的瞬間趣倾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工某饰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留儒恋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓黔漂,卻偏偏與公主長得像诫尽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炬守,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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