在學(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/x和x查看_tallRichHandsome內(nèi)存儲的值

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

可以看到,倒數(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的值為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_MASK的值0x0000000ffffffff8ULL,使用Mac電腦上的編程計算器將其轉(zhuǎn)化為二進制數(shù)

可以看到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指針的地址。

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

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

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_assoc和weakly_referenced的值都為0,接著我們person對象添加弱引用和關(guān)聯(lián)對象,來觀察一下has_assoc和weakly_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_assoc和weakly_referenced的值都變成了1。

值得注意的是:只要設(shè)置過關(guān)聯(lián)對象或弱引用過對象,has_assoc和weakly_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進行&(按位與)才可以取出其地址值。