之前在OC底層原理(二)這章內(nèi)容中有講過 instance對象的isa & ISA_MASK 指向class對象改含,class對象的isa & ISA_MASK指向meta-class對象,但是并沒有詳細講isa的內(nèi)部結(jié)構(gòu)
isa內(nèi)部結(jié)構(gòu)
在arm64架構(gòu)之前亲铡,isa就是一個普通指針,存儲著class對象或meta-class對象的地址
在arm64架構(gòu)之后绎速,蘋果用union結(jié)構(gòu)優(yōu)化了isa指針亩钟,讓isa能夠存儲更多的信息
在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
精簡一下代碼如下
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來做對比
struct和union內(nèi)存結(jié)構(gòu)示意圖
我們通過代碼來驗證下
創(chuàng)建一個命令行項目,在main函數(shù)中寫入如下代碼雄可,并運行
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的存儲的值互不影響凿傅,而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 共用一個內(nèi)存滞项,那么struct 內(nèi)部又是什么呢
這里要引申出另一個概念狭归,位域
位域
在了解位域之前我們先設想一個場景夭坪,我們需要存儲三個字段文判,高富帥,bool類型
按照我們原來的寫法我們會申明三個bool屬性來保存
@interface ZJPerson : NSObject
@property(nonatomic, assign) bool isTall;
@property(nonatomic, assign) bool isHandsome;
@property(nonatomic, assign) bool isRich;
@end
本來簡簡單單存三個bool變量室梅,用了三個字節(jié)有點浪費內(nèi)存
簡簡單單的0和1可以用1個字節(jié)的3bit來存儲戏仓,這就涉及位域數(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分配了一個字節(jié)的內(nèi)存敷待,其內(nèi)存示意圖如下
我們通過代碼來驗證下
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處打上斷點,運行
然后在控制臺查看testWeiYu的內(nèi)存地址仁热,再窺探這個地址存儲的數(shù)據(jù)
使用如下代碼查看其地址
p/x &(testWeiYu)
其輸出如下
然后再用如下代碼窺探其存儲數(shù)據(jù)
x 0x00007ffeefbff528
其輸出如下
06即為其存儲的數(shù)據(jù)
我們講0x06轉(zhuǎn)換成二進制數(shù)據(jù)如下
可以看到存儲的值為0b110
按照上面的內(nèi)存示意圖就是
我們看下最后的輸出
可以看到確實實現(xiàn)了1個字節(jié)存儲3個bool變量
另外細心的朋友可能也看到了NSLog里的兩個!!
NSLog(@"weiYu : %d %d %d", !!testWeiYu.a, !!testWeiYu.b, !!testWeiYu.c);
為什么要這樣寫呢
這又要涉及另外一個概念了榜揖,原碼、反碼和補碼
原碼抗蠢、反碼举哟、補碼
原碼
原碼就是第一位為符號位,0代表正迅矛,1代表負妨猩,別的位表示數(shù)值
比如+1原碼為
0000 0001
-1原碼為
1000 0001
所以8位2進制的原碼取值范圍為[-127, +127]
但是如果計算(+1) + (-1)的話,原碼計算結(jié)果如下
0000 0001 + 1000 0001 = 1000 0002
結(jié)果為-2,所以基于這種情況秽褒,反碼被發(fā)明了出來
反碼
如果為正數(shù)壶硅,原碼與反碼一致
如果為負數(shù),反碼為原碼除符號位外每一位取反
反碼就是在原碼的基礎(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
雖然反碼解決了正負相加等于0的問題庐椒,卻存在兩個0,+0和-0
所以蚂踊,再反碼的基礎(chǔ)上又提出了補碼的概念
補碼
如果為正數(shù)扼睬,反碼與補碼一致
如果為負數(shù),補碼為反碼+1,并丟棄最高位
反碼中-0表示為
1111 1111
補碼中-0表示為
1111 1111 + 0000 0001 = 1 0000 0000
丟棄最高位為
0000 0000
所以-0和+0表示一樣
原碼中-1的表示為
1000 0001
反碼為
1111 1110
補碼為
1111 1111
計算機采用的是補碼來存儲值
回到主題
NSLog(@"weiYu : %d %d %d", !!testWeiYu.a, !!testWeiYu.b, !!testWeiYu.c);
為什么要這樣寫呢悴势?
我們從反面角度來考慮窗宇,如果不這么寫會出現(xiàn)什么情況
NSLog(@"weiYu : %d %d %d", testWeiYu.a, testWeiYu.b, testWeiYu.c);
我們運行看下結(jié)果
為什么會打印-1呢?
因為char b是有符號類型特纤,而char b又只占用了1位军俊,所以會將1當作符號位負數(shù)來處理,計算機存儲值用的是補碼捧存,所以存儲的值為1取反再加上1粪躬,最后結(jié)果還是1,再加上符號位就打印出-1
我們也可以通過加a昔穴、b镰官、c申明為無符號類型來解決這個問題
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ù)據(jù)結(jié)構(gòu)來做的優(yōu)化
那么這種結(jié)構(gòu)的值的存與取又如何實現(xiàn)呢吗货?
我們用代碼來實現(xiàn)
申明一個ZJPerson類泳唠,它需要存儲三個bool變量isTall,isRich宙搬,isHandsome笨腥,使用共用體+位域技術(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脖母,那么共用體存的值應該如下
0000 0101
想要取tall的話士鸥,需要用這個值& 0000 0001
計算過程如下
0000 0101
&0000 0001
=0000 0001
同理,取rich谆级,需要用這個值& 0000 0010
同理烤礁,取handSome,需要用這個值& 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>
為什么要加上!!玻侥,是因為
tall值取出來是0000 0001,轉(zhuǎn)換成10進制就是1
rich值取出來是0000 0010亿蒸,轉(zhuǎn)換成10進制就是2
handsome值取出來是0000 0100凑兰,轉(zhuǎn)換成10進制就是4
取出來的值并不是bool類型,而取兩次反就可以得到正確的bool值
比如拿handsome的值4來做例子边锁,!4就是0姑食,!0就是1,1就是YES
再比如拿0來做例子茅坛,!0就是1音半,!1就是0,0就是NO
通過兩次取反運算就可以得到我們想要的bool值
我們繼續(xù)研究存值
如果我們要將tall的值設置為YES
就需要將共用體的值 | ZJTallMask
其運算過程如下
//原來tall值為NO
0000 0110
| 0000 0001
= 0000 0111
//原來tall值為YES
0000 0111
| 0000 0001
= 0000 0111
如果我們要將tall的值設置為NO贡蓖,那么|運算則不能完成這個任務了
那么要怎么實現(xiàn)存值為NO呢曹鸠?
首先,我們需要將掩碼按位取反斥铺,然后用共用體的值&這個取反的值就可以了
//將掩碼按位取反
~ZJTallMask//值為1111 1110
//然后再做&運算
//原來tall值為NO
0000 0110
| 1111 1110
= 0000 0110
//原來tall值為YES
0000 0111
| 1111 1110
= 0000 0111
這樣NO的存儲也完成了
我們完善代碼如下
#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)三個bool變量的存取功能就完成了
我們測試一下
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對象地址或者meta-class對象地址
我們看看掩碼的值
define ISA_MASK 0x0000000ffffffff8ULL
將其轉(zhuǎn)換成2進制
再看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里存儲的就是class對象晾蜘、meta-class對象的地址
isa_t其他字段的釋義
union isa_t {
uintptr_t bits;
struct {
//arm64
// 是否開啟 isa 指針優(yōu)化
uintptr_t nonpointer : 1; \
// 是否有設置過關(guān)聯(lián)對象邻眷,如果沒有,釋放時會更快
uintptr_t has_assoc : 1; \
// 是否有 C++ 的析構(gòu)函數(shù)(.cxx_destruct)剔交,如果沒有肆饶,釋放時會更快
uintptr_t has_cxx_dtor : 1; \
存儲著Class、Meta-Class對象的內(nèi)存地址信息
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
// 用于在調(diào)試時分辨對象是否未完成初始化
uintptr_t magic : 6; \
// 是否有被弱引用指向過岖常,如果沒有驯镊,釋放時會更快
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
// 引用計數(shù)器是否過大無法存儲在 isa 中。如果為 1,那么引用計數(shù)會存儲在一個叫 SideTable 的類的屬性中
uintptr_t has_sidetable_rc : 1; \
// 里面存儲的值是引用計數(shù) - 1
uintptr_t extra_rc : 19
};
};