OC底層原理(九):runtime之isa

之前在OC底層原理(二)這章內(nèi)容中有講過 instance對(duì)象的isa & ISA_MASK 指向class對(duì)象,class對(duì)象的isa & ISA_MASK指向meta-class對(duì)象,但是并沒有詳細(xì)講isa的內(nèi)部結(jié)構(gòu)

isa內(nèi)部結(jié)構(gòu)

在arm64架構(gòu)之前,isa就是一個(gè)普通指針,存儲(chǔ)著class對(duì)象或meta-class對(duì)象的地址
在arm64架構(gòu)之后,蘋果用union結(jié)構(gòu)優(yōu)化了isa指針,讓isa能夠存儲(chǔ)更多的信息
在objc4源碼中搜索isa_t,其結(jié)構(gòu)如下

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 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

// SUPPORT_PACKED_ISA
#endif

精簡(jiǎn)一下代碼如下

union isa_t {
    uintptr_t bits;
    struct {
        //arm64
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
    };
};

union 共用體

那么什么是共用體呢,就是共用體內(nèi)部的成員變量都共享一塊內(nèi)存區(qū)域

union testUnion {
    int a;
    int b;
    int c;
};

struct testStruct {
    int a;
    int b;
    int c;
};

我們通過union和struct來做對(duì)比

struct和union內(nèi)存結(jié)構(gòu)示意圖
內(nèi)存示意圖.png

我們通過代碼來驗(yàn)證下
創(chuàng)建一個(gè)命令行項(xiàng)目,在main函數(shù)中寫入如下代碼,并運(yùn)行

union testUnion {
    int a;
    int b;
    int c;
};

struct testStruct {
    int a;
    int b;
    int c;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        union testUnion testUnion;
        testUnion.a = 10;
        testUnion.b = 11;
        testUnion.c = 12;
        NSLog(@"union : %d  %d   %d", testUnion.a, testUnion.b, testUnion.c);
        
        struct testStruct testStruct;
        testStruct.a = 10;
        testStruct.b = 11;
        testStruct.c = 12;
        NSLog(@"struct : %d  %d   %d", testStruct.a, testStruct.b, testStruct.c);
    }
    return 0;
}

輸出結(jié)果如下


輸出結(jié)果.png

可以看到struct的存儲(chǔ)的值互不影響,而union里的a、b的值被c的值給覆蓋了
我們回到isa_t的結(jié)構(gòu)中來看

union isa_t {
    uintptr_t bits;
    struct {
        //arm64
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
    };
};

可以看出 uintptr_t bits 和 struct 共用一個(gè)內(nèi)存,那么struct 內(nèi)部又是什么呢
這里要引申出另一個(gè)概念,位域


位域

在了解位域之前我們先設(shè)想一個(gè)場(chǎng)景,我們需要存儲(chǔ)三個(gè)字段,高富帥,bool類型
按照我們?cè)瓉淼膶懛ㄎ覀儠?huì)申明三個(gè)bool屬性來保存

@interface ZJPerson : NSObject
@property(nonatomic, assign) bool isTall;
@property(nonatomic, assign) bool isHandsome;
@property(nonatomic, assign) bool isRich;
@end

本來簡(jiǎn)簡(jiǎn)單單存三個(gè)bool變量,用了三個(gè)字節(jié)有點(diǎn)浪費(fèi)內(nèi)存
簡(jiǎn)簡(jiǎn)單單的0和1可以用1個(gè)字節(jié)的3bit來存儲(chǔ),這就涉及位域數(shù)據(jù)結(jié)構(gòu)了
位域的寫法和結(jié)構(gòu)體類似

struct testWeiYu {
    char a : 1;
    char b : 1;
    char c : 1;
};

這代表著a占用1bit,不是字節(jié)(一字節(jié)等于8bit),b占用1bit,c占用1bit
比如testWeiYu分配了一個(gè)字節(jié)的內(nèi)存,其內(nèi)存示意圖如下


內(nèi)存示意圖.png

我們通過代碼來驗(yàn)證下

struct testWeiYu {
    char a : 1;
    char b : 1;
    char c : 1;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        struct testWeiYu testWeiYu;
        testWeiYu.a = 0;
        testWeiYu.b = 1;
        testWeiYu.c = 1;
        NSLog(@"weiYu : %d  %d   %d", !!testWeiYu.a, !!testWeiYu.b, !!testWeiYu.c);
        
    }
    return 0;
}

然后在NSLog處打上斷點(diǎn),運(yùn)行
然后在控制臺(tái)查看testWeiYu的內(nèi)存地址,再窺探這個(gè)地址存儲(chǔ)的數(shù)據(jù)
使用如下代碼查看其地址

p/x &(testWeiYu)

其輸出如下


地址.png

然后再用如下代碼窺探其存儲(chǔ)數(shù)據(jù)

x 0x00007ffeefbff528

其輸出如下


數(shù)據(jù).png

06即為其存儲(chǔ)的數(shù)據(jù)
我們講0x06轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)如下


二進(jìn)制.png

可以看到存儲(chǔ)的值為0b110
按照上面的內(nèi)存示意圖就是
內(nèi)存示意圖.png

我們看下最后的輸出


輸出結(jié)果.png

可以看到確實(shí)實(shí)現(xiàn)了1個(gè)字節(jié)存儲(chǔ)3個(gè)bool變量
另外細(xì)心的朋友可能也看到了NSLog里的兩個(gè)!!
NSLog(@"weiYu : %d  %d   %d", !!testWeiYu.a, !!testWeiYu.b, !!testWeiYu.c);

為什么要這樣寫呢
這又要涉及另外一個(gè)概念了,原碼、反碼和補(bǔ)碼

原碼、反碼、補(bǔ)碼

原碼

原碼就是第一位為符號(hào)位,0代表正,1代表負(fù),別的位表示數(shù)值
比如+1原碼為

0000 0001

-1原碼為

1000 0001

所以8位2進(jìn)制的原碼取值范圍為[-127, +127]
但是如果計(jì)算(+1) + (-1)的話,原碼計(jì)算結(jié)果如下

0000 0001 + 1000 0001 = 1000 0002 

結(jié)果為-2,所以基于這種情況,反碼被發(fā)明了出來

反碼
如果為正數(shù),原碼與反碼一致
如果為負(fù)數(shù),反碼為原碼除符號(hào)位外每一位取反
反碼就是在原碼的基礎(chǔ)上,每一位取反
如原碼中-0的表示為

1000 0000

反碼為

1111 1111

原碼中+0的表示為

0000 0000

反碼為

0000 0000

原碼中+1的表示為

0000 0001

反碼為

0000 0001

原碼中-1的表示為

1000 0001

反碼為

1111 1110

所以(+1) + (-1)等于-0

1000 0001 + 1111 1110 = 1111 1111

雖然反碼解決了正負(fù)相加等于0的問題,卻存在兩個(gè)0,+0和-0
所以,再反碼的基礎(chǔ)上又提出了補(bǔ)碼的概念

補(bǔ)碼
如果為正數(shù),反碼與補(bǔ)碼一致
如果為負(fù)數(shù),補(bǔ)碼為反碼+1,并丟棄最高位
反碼中-0表示為

1111 1111

補(bǔ)碼中-0表示為

1111 1111 + 0000 0001 = 1 0000 0000
丟棄最高位為 
0000 0000

所以-0和+0表示一樣

原碼中-1的表示為

1000 0001

反碼為

1111 1110

補(bǔ)碼為

1111 1111

計(jì)算機(jī)采用的是補(bǔ)碼來存儲(chǔ)值


回到主題

NSLog(@"weiYu : %d  %d   %d", !!testWeiYu.a, !!testWeiYu.b, !!testWeiYu.c);

為什么要這樣寫呢?
我們從反面角度來考慮,如果不這么寫會(huì)出現(xiàn)什么情況

NSLog(@"weiYu : %d  %d   %d", testWeiYu.a, testWeiYu.b, testWeiYu.c);

我們運(yùn)行看下結(jié)果


結(jié)果.png

為什么會(huì)打印-1呢?
因?yàn)閏har b是有符號(hào)類型,而char b又只占用了1位,所以會(huì)將1當(dāng)作符號(hào)位負(fù)數(shù)來處理,計(jì)算機(jī)存儲(chǔ)值用的是補(bǔ)碼,所以存儲(chǔ)的值為1取反再加上1,最后結(jié)果還是1,再加上符號(hào)位就打印出-1
我們也可以通過加a、b、c申明為無符號(hào)類型來解決這個(gè)問題

struct testWeiYu {
    unsigned char a : 1;
    unsigned char b : 1;
    unsigned char c : 1;
};

共用體+位域
在了解了共用體和位域的概念后,我們回過頭看isa_t的結(jié)構(gòu)

union isa_t {
    uintptr_t bits;
    struct {
        //arm64
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
    };
};

可以看到isa其實(shí)就是運(yùn)用了共用體+位域的數(shù)據(jù)結(jié)構(gòu)來做的優(yōu)化
那么這種結(jié)構(gòu)的值的存與取又如何實(shí)現(xiàn)呢?
我們用代碼來實(shí)現(xiàn)
申明一個(gè)ZJPerson類,它需要存儲(chǔ)三個(gè)bool變量isTall,isRich,isHandsome,使用共用體+位域技術(shù)來實(shí)現(xiàn)存值與取值

//.h
@interface ZJPerson : NSObject
- (void)setTall:(BOOL)tall;
- (BOOL)tall;
- (void)setRich:(BOOL)rich;
- (BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)handsome;
@end

//.m
@interface ZJPerson()
{
    union man {
        char bits;
        struct {
            unsigned char isTall;
            unsigned char isRich;
            unsigned char isHandsome;
        };
    }man;
}
@end

@implementation ZJPerson

- (void)setTall:(BOOL)tall {
    
}

- (BOOL)tall {
    return NO;
}

- (void)setRich:(BOOL)rich {
    
}

- (BOOL)rich {
    return NO;
}

- (void)setHandsome:(BOOL)handsome {
    
}

- (BOOL)handsome {
    return NO;
}

@end

我們首先考慮取值
比如tall為YES,rich為NO,handSome為YES,那么共用體存的值應(yīng)該如下

0000 0101

想要取tall的話,需要用這個(gè)值& 0000 0001
計(jì)算過程如下

 0000 0101
&0000 0001
=0000 0001

同理,取rich,需要用這個(gè)值& 0000 0010
同理,取handSome,需要用這個(gè)值& 0000 0100
可以看到,取最右邊的一位需要&(1<<0)
取右邊的第二位需要&(1<<1)
取右邊的第三位需要&(1<<2)
那么我們來更新.m文件中的代碼,申明三位掩碼來分別取tall,rich和handsome的值

#define ZJTallMask (1<<0)
#define ZJRichMask (1<<1)
#define ZJHandSome (1<<2)
@interface ZJPerson()
{
    union man {
        char bits;
        struct {
            unsigned char isTall;
            unsigned char isRich;
            unsigned char isHandsome;
        };
    }man;
}
@end

@implementation ZJPerson

- (void)setTall:(BOOL)tall {
    
}

- (BOOL)tall {
    return !!(man.bits & ZJTallMask);
}

- (void)setRich:(BOOL)rich {
    
}

- (BOOL)rich {
    return !!(man.bits & ZJRichMask);
}

- (void)setHandsome:(BOOL)handsome {
    
}

- (BOOL)handsome {
    return !!(man.bits & ZJHandSome);
}

@end

這樣,我們?nèi)≈稻屯瓿闪?br> 為什么要加上!!,是因?yàn)?br> tall值取出來是0000 0001,轉(zhuǎn)換成10進(jìn)制就是1
rich值取出來是0000 0010,轉(zhuǎn)換成10進(jìn)制就是2
handsome值取出來是0000 0100,轉(zhuǎn)換成10進(jìn)制就是4
取出來的值并不是bool類型,而取兩次反就可以得到正確的bool值
比如拿handsome的值4來做例子,!4就是0,!0就是1,1就是YES
再比如拿0來做例子,!0就是1,!1就是0,0就是NO
通過兩次取反運(yùn)算就可以得到我們想要的bool值

我們繼續(xù)研究存值
如果我們要將tall的值設(shè)置為YES
就需要將共用體的值 | ZJTallMask
其運(yùn)算過程如下

//原來tall值為NO
  0000 0110
| 0000 0001
= 0000 0111

//原來tall值為YES
  0000 0111
| 0000 0001
= 0000 0111

如果我們要將tall的值設(shè)置為NO,那么|運(yùn)算則不能完成這個(gè)任務(wù)了
那么要怎么實(shí)現(xiàn)存值為NO呢?
首先,我們需要將掩碼按位取反,然后用共用體的值&這個(gè)取反的值就可以了

//將掩碼按位取反
~ZJTallMask//值為1111 1110

//然后再做&運(yùn)算
//原來tall值為NO
  0000 0110
| 1111 1110
= 0000 0110

//原來tall值為YES
  0000 0111
| 1111 1110
= 0000 0111

這樣NO的存儲(chǔ)也完成了
我們完善代碼如下

#define ZJTallMask (1<<0)
#define ZJRichMask (1<<1)
#define ZJHandSome (1<<2)
@interface ZJPerson()
{
    union man {
        char bits;
        struct {
            unsigned char isTall;
            unsigned char isRich;
            unsigned char isHandsome;
        };
    }man;
}
@end

@implementation ZJPerson

- (void)setTall:(BOOL)tall {
    if (tall) {
        man.bits |= ZJTallMask;
    }else {
        man.bits &= ~ZJTallMask;
    }
}

- (BOOL)tall {
    return !!(man.bits & ZJTallMask);
}

- (void)setRich:(BOOL)rich {
    if (rich) {
        man.bits |= ZJRichMask;
    }else {
        man.bits &= ~ZJRichMask;
    }
}

- (BOOL)rich {
   return !!(man.bits & ZJRichMask);
}

- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        man.bits |= ZJHandSome;
    }else {
        man.bits &= ~ZJHandSome;
    }
}

- (BOOL)handsome {
    return !!(man.bits & ZJHandSome);
}

@end

這樣,通過共用體+位域?qū)崿F(xiàn)三個(gè)bool變量的存取功能就完成了
我們測(cè)試一下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        ZJPerson *person = [[ZJPerson alloc]init];
        person.tall = YES;
        person.rich = YES;
        person.handsome = YES;
        NSLog(@"tall:%d rich:%d handsome:%d", person.tall, person.rich, person.handsome);
    }
    return 0;
}
結(jié)果.png

之前我們說的arm64之后需要isa指針&Mask獲取class對(duì)象地址或者meta-class對(duì)象地址
我們看看掩碼的值

define ISA_MASK        0x0000000ffffffff8ULL

將其轉(zhuǎn)換成2進(jìn)制


二進(jìn)制.png

再看isa_t結(jié)構(gòu)

union isa_t {
    uintptr_t bits;
    struct {
        //arm64
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
    };
};

isa & Mask之后就是將shiftcls的值取出來,而shiftcls里存儲(chǔ)的就是class對(duì)象、meta-class對(duì)象的地址


isa_t其他字段的釋義

union isa_t {
    uintptr_t bits;
    struct {
        //arm64
        // 是否開啟 isa 指針優(yōu)化
        uintptr_t nonpointer        : 1;                                       \
        // 是否有設(shè)置過關(guān)聯(lián)對(duì)象,如果沒有,釋放時(shí)會(huì)更快
        uintptr_t has_assoc         : 1;                                       \
        // 是否有 C++ 的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放時(shí)會(huì)更快
        uintptr_t has_cxx_dtor      : 1;                                       \
        存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        // 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
        uintptr_t magic             : 6;                                       \
        // 是否有被弱引用指向過,如果沒有,釋放時(shí)會(huì)更快
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        // 引用計(jì)數(shù)器是否過大無法存儲(chǔ)在 isa 中。如果為 1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 SideTable 的類的屬性中
        uintptr_t has_sidetable_rc  : 1;                                       \
        // 里面存儲(chǔ)的值是引用計(jì)數(shù) - 1
        uintptr_t extra_rc          : 19
    };
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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