之前在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)示意圖

我們通過代碼來驗(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é)果如下

可以看到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)存示意圖如下

我們通過代碼來驗(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)
其輸出如下

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

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

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

我們看下最后的輸出

可以看到確實(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é)果

為什么會(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;
}

之前我們說的arm64之后需要isa指針&Mask獲取class對(duì)象地址或者meta-class對(duì)象地址
我們看看掩碼的值
define ISA_MASK 0x0000000ffffffff8ULL
將其轉(zhuǎn)換成2進(jìn)制

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