iOS中的Runtime詳解1(附面試題) - 底層原理總結(jié)

在學(xué)習(xí)Runtime之前首先要對isa和superclass有一定了解,關(guān)于isa和superclass,可以看一下我的另一篇文章iOS中OC對象的本質(zhì)詳解(附面試題) - 底層原理總結(jié)

每個OC對象都有一個isa指針,在arm64架構(gòu)之前,isa僅僅是一個指針,保存著對象在內(nèi)存中的地址,實例對象通過isa可以直接拿到類對象,類對象通過isa可以直接拿到元類對象。而在arm64架構(gòu)之后,蘋果對isa進行了優(yōu)化,變成了一個共用體結(jié)構(gòu)(union),同時使用位域來存儲更多的信息。這時候要想通過實例對象要想通過isa拿類對象,就要通過位運算從isa中獲取類對象在內(nèi)存中的地址,類對象中的isa也是如此。下面具體來看一下。

一、共用體 - 蘋果用它在二進制層面優(yōu)化isa

源碼中的isa

struct objc_object {
private:
    isa_t isa;
}

isa內(nèi)部

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類型,union表示共用體。什么是共用體呢?先來看看比較拗口的官方定義,看完可能有點懵,不過沒關(guān)系,接著往下看就會搞明白的。

共用體:在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段內(nèi)存單元中。也就是使用覆蓋技術(shù),幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu),在C語言中,被稱作“共用體”類型結(jié)構(gòu),簡稱共用體,也叫聯(lián)合體。

二、模仿底層使用共用體

2.1 很多時候,我們在浪費存儲空間

在arm64之前,OC對象中的isa僅僅是一個普通的指針,存著對象的地址。在arm64之后,蘋果對isa進行了優(yōu)化,變成了一個共用體結(jié)構(gòu),同時使用位域來存儲更多的信息。

同樣的內(nè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 {
        Person *ps = [[Person alloc] init];
        ps.tall = YES;
        ps.rich = YES;
        ps.handsome = NO;
        NSLog(@"%d %d %d",ps.isTall,ps.isRich,ps.isHansome);
    }
    return 0;
}

上面的代碼中Person有3個BOOL類型的屬性,每個BOOL類型的屬性,占用1個字節(jié)內(nèi)存空間,3個BOOL類型也就是3個字節(jié)內(nèi)存空間。

1個字節(jié)內(nèi)存空間有8個二進制位,這3個BOOL類型開辟了24個二進制位!然而實際上,BOOL類型只需要1個二進制位0或1存儲就足夠了!也就是說這3個BOOL類型的屬性浪費了21個二進制位的存儲空間,這簡直是一種極大的浪費!就我們平時編寫代碼來說,可能沒什么,但是從操作系統(tǒng)層面來看,這可能積累起很大的浪費。

2.2 使用一個char類型來存儲以上3個BOOL類型的值

char類型占據(jù)一個字節(jié)內(nèi)存空間,即8個二進制位,我們可以添加一個char類型的成員變量,使用它的其中3個二進制位來存儲3個BOOL類型的值。

@interface Person()
{
    char _tallRichHandsome;
}

例如_tallRichHandsome的值為 0b 0000 0000,那么可以只使用8個二進制位中的最后3個,分別為其賦值0或1來代表tall、rich、handsome的值。如下圖

二進制位的存儲

問題是我們該如何對其進行取值賦值操作呢?

取值

假如char類型的成員變量中存儲的二進制位 0b 0000 0010,如果想將倒數(shù)第二位也就是rich的值取出來,可以使用&(按位與)運算,將它取出來。

&:按位與,同真為真,其他都為假。

// 示例
// 取出倒數(shù)第三位 tall
  0000 0010
& 0000 0100
------------
  0000 0000  // 取出倒數(shù)第三位的值為0,其他位都置為0

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

// 取出倒數(shù)第一位 handsome
  0000 0010
& 0000 0001
------------
  0000 0000 // 取出倒數(shù)第一位的值為0,其他位都置為0

按位與可以用來取出特定的位,想取出哪一位就將那一位置為1,其他位都置為0,然后同原數(shù)據(jù)進行按位與,即可取出特定的位。

那么我們就可以通過以下這種方式重寫3個BOOL類型的getter方法來取值

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

上面的代碼使用2個!!(非),是將按位與取出的值改為BOOL類型。一個!可以將按位與的值改為BOOL類型,但是取反了,所以再使用一個!取反,得到我們真正想要的結(jié)果。比如

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

這里我們?nèi)〕龅闹禐?0000 0010,也就是十進制的2。但是我們需要返回的是一個BOOL類型的值0或1。這里_tallRichHandsome的倒數(shù)第2位為1,所以取出rich的值應(yīng)為1,我們通過1個!將2轉(zhuǎn)為0,再通過!將0轉(zhuǎn)為1,即!!(_tallRichHandsome & TichMask)的結(jié)果為1,我們做到了獲取正確的值。

掩碼:上述代碼中定義了3個宏,用來分別進行按位與運算而取出相應(yīng)的值。一般的,用來進行按位與(&)運算的值我們稱之為掩碼。

為了可讀性,上述3個宏的定義可以使用 <<(左移) 來優(yōu)化。如 0000 0001 就是讓1左移0位,用 1<<0 表示,0000 0010 就是讓1左移1位,用 1<<1表示,0000 0100 就是讓1左移2位,用 1<<2 表示。那么上述宏定義可以這樣優(yōu)化

#define TallMask (1<<2) // 0b00000100 4
#define RichMask (1<<1) // 0b00000010 2
#define HandsomeMask (1<<0) // 0b00000001 1

設(shè)值

設(shè)值就是將char類型8位中的某一位設(shè)值為0或1,可以使用|(按位或) 運算設(shè)值。

&:按位或,只要有一個為1即為1,否則為0。

如果要將某一位置為1的話,那么將原本的值與掩碼進行按位或即可,比如我們將tall置為1

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

如果要將某一位置為0的話,需要將掩碼~(按位取反),然后再與原來的值進行按位與。

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

所以對于之前的3個BOOL類型,setter方法可以如下實現(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àn)的結(jié)果。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *ps = [[Person alloc] init];
        ps.tall = YES;
        ps.rich = YES;
        ps.handsome = NO;
        NSLog(@"%d %d %d",ps.isTall,ps.isRich,ps.isHansome);
    }
    return 0;
}
2019-12-18 11:31:48.375172+0800 Runtime[53571:649155] 1 1 0
Program ended with exit code: 0

可以看到,經(jīng)過驗證,我們上述做法實現(xiàn)了使用char類型8個二進制位的后3位,實現(xiàn)3個BOOL類型的賦值取值。但是上述代碼的可讀性有待提高,維護效率也有待提高。接下來使用結(jié)構(gòu)體的位域來優(yōu)化上述代碼。

位域

位域聲明:位域名:位域長度

位域注意事項:

1.如果1個字節(jié)所??臻g不夠存放另一位域時,應(yīng)從下一單元起存放該位域。也可以直接使某位域從下一單元開始。
2.位域的長度不能大于數(shù)據(jù)類型本身的長度,比如int類型就不能超過32位二進制位。
3.位域可以無位域名,這時它只用來做填充或調(diào)整位置。無名的位域是不能使用的。

前面的代碼使用結(jié)構(gòu)體位域優(yōu)化之后

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

setter、getter方法中可以直接通過結(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;
}

在NSLog出打個斷點,通過p/xx查看_tallRichHandsome內(nèi)存儲的值

_tallRichHandsome內(nèi)存儲的值

_tallRichHandsome占據(jù)一個內(nèi)存空間,也就是8個二進制位,我們通過Mac電腦自帶的計算器將05這個十六進制數(shù)轉(zhuǎn)化為二進制數(shù)查看

05在二進制中

可以看到,倒數(shù)第3位也就是tall的值為1,倒數(shù)第2位也就是rich的值為0,倒數(shù)第1位也是就handsome的值為1,和前面代碼中我們設(shè)置的值是一樣的。說明我們成功做到了。

但是打印的結(jié)果似乎有點問題,tall和handsome居然為-1。設(shè)值的時候,我們設(shè)置的是YES,應(yīng)該打印1才對,為什么變成-1了呢?

2019-12-18 11:31:48.375172+0800 Runtime[53571:649155] tall:-1,rich:0,handsome:-1
Program ended with exit code: 0

來到getter方法內(nèi)部,通過打印斷點查看獲取到的值。

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

打印ret的值

打印ret的值

打印出來ret的值為255,也就是1111 1111,在一個字節(jié)時,有符號數(shù)則為-1,無符號數(shù)則為255。因此我們在打印的時候出現(xiàn)了-1。

我們通過結(jié)構(gòu)體獲取到handsome的值為一個字節(jié)8個二進制位中的一位,而BOOL類型占據(jù)一個字節(jié)8個二進制位,當(dāng)僅有1位的值擴展為8位的時候,其余空位就會根據(jù)前面一位的值全部部位成1。所以當(dāng)我們使用一個二進制位1的handsome給BOOL賦值的時候,導(dǎo)致BOOL中的8個二進制位0000 0000全被映射為了1,即1111 1111。

為了解決這個問題,我們可以將tall、rich、handsome的值設(shè)置為占據(jù)2個二進制位。這樣當(dāng)我們使用2個二進制位01的handsome給8個二進制位的BOOL類型賦值的時候,前面的空值就會自動根據(jù)前面一位補全為0,即 0000 0001,因此這時打印出來的值為1。

同樣的,上述問題也可以使用!!來解決問題,達到我們想要的效果。

使用結(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)體的位域,則不再需要使用掩碼,使代碼可讀性增強了不少,但是效率相比使用位運算要低,如果想要高效率又想高可讀性,那么就要使用到共用體了。

共用體

為了使代碼存取高效率的同時,又有較強的可讀性,可以使用共用體來增強代碼的可讀性,同時使用位運算來提高數(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)體僅僅是為了增強代碼可讀性,無實質(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);
}

上面的代碼中使用位運算這種高效的方式存取值,使用union共用體來對數(shù)據(jù)進行存儲。增加存取效率的同時提高了代碼可讀性。

其中_tallRichHandsome共用體只占用一個字節(jié),因為結(jié)構(gòu)體中tall、rich、handsome都只占1個二進制位空間,所以結(jié)構(gòu)體只占一個字節(jié)空間,而char類型的bits也只占用一個字節(jié)空間,它們都在共用體中,它們共用同一塊內(nèi)存空間,即共用一個字節(jié)的內(nèi)存空間即可。

并且在getter、setter方法中并沒有用到共用體,結(jié)構(gòu)體僅僅為了增加代碼可讀性,指明共用體重存儲了哪些值,以及這些值占多少個二進制位內(nèi)存空間。同時存取值使用位運算來提高效率,存儲使用共用體,存放的位置還是通過掩碼進行位運算來控制。

至此,優(yōu)化工作就完成了,優(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)過上面對位運算、位域以及共用體的分析,現(xiàn)在再來看源碼我們就可以很好的理解其中的內(nèi)容。源碼中通過共用體存儲了64個二進制位的值,這些值在結(jié)構(gòu)體中被展示出來,通過對bits進行位運算而取出相應(yīng)位置的值。

這里主要關(guān)注一下shiftcls,它里面存儲著類對象、元類對象的內(nèi)存地址,實例對象的isa指針需要和ISA_MASK進行&(按位與)運算才能得到真正的類對象在內(nèi)存中的地址。

isa指針需要經(jīng)過按位與得到類對象的地址

我們再來看看ISA_MASK的值0x0000000ffffffff8ULL,使用Mac電腦上的編程計算器將其轉(zhuǎn)化為二進制數(shù)

ISA_MASK在二進制中

可以看到ISA_MASK的值轉(zhuǎn)化為二進制后,其中有33位都為1,此時我們已經(jīng)知道,用這個33個1通過&(按位與)可以取出對應(yīng)位的值。即通過ISA_MASK可以取出類對象或元類對象在內(nèi)存中的地址值。

值得注意的是,ISA_MASK最后3位的值為0,所以任何數(shù)通過ISA_MASK按位與得到的數(shù),最后3位必定都為0,轉(zhuǎn)化為十六進制末位必定為8或0。

四、isa中存儲的信息及作用

isa中存儲的信息及作用

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

驗證

我們通過下面一段代碼來驗證一下上述注釋,需要注意的是,應(yīng)當(dāng)在真機上運行,因為真機上才是__arm64__架構(gòu),而模擬器上是x86架構(gòu)。

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    NSLog(@"%p",[person class]);
    NSLog(@"%@",person);
}

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

打印結(jié)果

將類對象的地址值轉(zhuǎn)換為二進制

類對象地址

將person對象ps的isa指針地址轉(zhuǎn)換為二進制

person對象的isa指針地址

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

extra_rc:extra_rc的19位中存儲著的值為引用計數(shù)-1,因為此時person的引用計數(shù)為1,因此此時extra_rc的19位二進制中存儲的是0。

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

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

因為此時person對象沒有關(guān)聯(lián)對象并且沒有弱指針引用過,可以看出has_assocweakly_referenced的值都為0,接著我們person對象添加弱引用和關(guān)聯(lián)對象,來觀察一下has_assocweakly_referenced的變化。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [[Person alloc] init];
    NSLog(@"%p",[Person class]);
    __weak Person *weakPerson = person;
    objc_setAssociatedObject(person, @"name", @"jack", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSLog(@"%@",person);
}

現(xiàn)在再重新打印person的isa指針地址,用Mac自帶的計算器,將其轉(zhuǎn)化為二進制可以看到has_assocweakly_referenced的值都變成了1。

has_assoc和weakly_referenced的變化

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

如果沒有設(shè)置過關(guān)聯(lián)對象,對象釋放時會更快,這是因為對象在銷毀時會判斷是否有關(guān)聯(liá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;
}

希望到這里大家能對isa指針有一個新的認識,__arm64__架構(gòu)之后,isa指針不再只是只存儲Class(類對象)Meta-Class(元類對象)的地址。而是使用共用體的方式充分利用了8個字節(jié),64個二進制位存儲了更多信息,其中shiftcls占用33個二進制位,存儲了Class(類對象)Meta-Class(元類對象)的地址,需要同ISA_MASK進行&(按位與)才可以取出其地址值。

下一篇iOS中的Runtime詳解2(附面試題 - 底層原理總結(jié))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 感恩青島一直陽光明媚!居然在青島待了10天,收獲滿滿!體驗不一樣的青島,這就是我的第二故鄉(xiāng)! 感恩陳老師創(chuàng)建了超凡...
    幸福儲錢罐閱讀 467評論 0 1
  • 少女曹七巧被家人半騙半賣,送進了大家族姜家做了二房少奶奶,二少爺是個勉強坐起來還沒有三歲孩子高的軟骨殘疾人。在大家...
    一顆柚心閱讀 526評論 4 5
  • 老趙買房遇麻煩 老王腳踏兩只船 這邊收了賣房錢 那邊出租二十年 老趙買房付完錢,辦了手續(xù),可老王賣房遲遲不交鑰匙,...
    旖旎i閱讀 369評論 1 16
  • 1 “哎哎哎,你們快來看啊,這兒有一只兔子!” 蔣琳像發(fā)現(xiàn)了新大陸似的突然大叫起來,她興奮的叫聲把正百無聊賴東扯葫...
    孔孟之鄉(xiāng)閱讀 205評論 0 0
  • 培養(yǎng)一種節(jié)奏,奔向繁華。
    愉悅的右腦閱讀 202評論 0 2

友情鏈接更多精彩內(nèi)容