內(nèi)存管理之Tagged pointer
iOS開(kāi)發(fā)者對(duì)引用計(jì)數(shù)這個(gè)名詞肯定不陌生,引用計(jì)數(shù)是蘋(píng)果為了方便開(kāi)發(fā)者管理內(nèi)存而引入的一個(gè)概念,當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)象就會(huì)被釋放。但是,真的是所有對(duì)象都是這樣嗎?
內(nèi)存分配
iOS將虛擬內(nèi)存按照地址由低到高劃分為如下五個(gè)區(qū):
[圖片上傳失敗...(image-b07219-1625042343370)]
在程序運(yùn)行時(shí),代碼區(qū),常量區(qū)以及全局靜態(tài)區(qū)的大小是固定的,會(huì)變化的只有棧和堆的大小。而棧的內(nèi)存是有操作系統(tǒng)自動(dòng)釋放的,我們平常說(shuō)所的iOS內(nèi)存引用計(jì)數(shù),其實(shí)是就堆上的對(duì)象來(lái)說(shuō)的。
如何引入tagged pointer
自2013年蘋(píng)果推出iphone5s之后,iOS的尋址空間擴(kuò)大到了64位。我們可以用63位來(lái)表示一個(gè)數(shù)字(一位做符號(hào)位)。那么這個(gè)數(shù)字的范圍是2^63 ,很明顯我們一般不會(huì)用到這么大的數(shù)字,那么在我們定義一個(gè)數(shù)字時(shí)NSNumber *num = @100,實(shí)際上內(nèi)存中浪費(fèi)了很多的內(nèi)存空間。
當(dāng)然蘋(píng)果肯定也認(rèn)識(shí)到了這個(gè)問(wèn)題,于是就引入了tagged pointer,tagged pointer是一種特殊的“指針”,其特殊在于,其實(shí)它存儲(chǔ)的并不是地址,而是真實(shí)的數(shù)據(jù)和一些附加的信息。
我們可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋(píng)果對(duì)于Tagged Pointer特點(diǎn)的介紹:

- Tagged Pointer專(zhuān)門(mén)用來(lái)存儲(chǔ)小的對(duì)象,例如NSNumber, NSDate, NSString。
- Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free。
- 在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時(shí)比以前快106倍。
NSTaggedPointer
我們先看下下面這段代碼:
NSMutableString *mutableStr = [NSMutableString string];
NSString *immutable = nil;
#define _OBJC_TAG_MASK (1UL<<63)
char c = 'a';
do {
[mutableStr appendFormat:@"%c", c++];
immutable = [mutableStr copy];
NSLog(@"%p %@ %@", immutable, immutable, immutable.class);
}while(((uintptr_t)immutable & _OBJC_TAG_MASK) == _OBJC_TAG_MASK);
運(yùn)行結(jié)果:
2020-08-08 14:15:54.480862+0800 TaggedPointerDemo[55468:2078125] 0xdc5050684e86e57c a NSTaggedPointerString
2020-08-08 14:15:54.481719+0800 TaggedPointerDemo[55468:2078125] 0xdc5050684e80c57f ab NSTaggedPointerString
2020-08-08 14:15:54.482480+0800 TaggedPointerDemo[55468:2078125] 0xdc50506848b0c57e abc NSTaggedPointerString
2020-08-08 14:15:54.483342+0800 TaggedPointerDemo[55468:2078125] 0xdc50506e08b0c579 abcd NSTaggedPointerString
2020-08-08 14:15:54.483950+0800 TaggedPointerDemo[55468:2078125] 0xdc50563e08b0c578 abcde NSTaggedPointerString
2020-08-08 14:15:54.484246+0800 TaggedPointerDemo[55468:2078125] 0xdc56363e08b0c57b abcdef NSTaggedPointerString
2020-08-08 14:15:54.484800+0800 TaggedPointerDemo[55468:2078125] 0xda26363e08b0c57a abcdefg NSTaggedPointerString
2020-08-08 14:15:54.485200+0800 TaggedPointerDemo[55468:2078125] 0xdc527050ee978a35 abcdefgh NSTaggedPointerString
2020-08-08 14:15:54.485644+0800 TaggedPointerDemo[55468:2078125] 0xdcd85e404adcb774 abcdefghi NSTaggedPointerString
2020-08-08 14:15:54.486003+0800 TaggedPointerDemo[55468:2078125] 0x28334c2c0 abcdefghij __NSCFString
上圖我們可以看到,當(dāng)字符串的長(zhǎng)度為10個(gè)以?xún)?nèi)時(shí),字符串的類(lèi)型都是NSTaggedPointerString類(lèi)型,當(dāng)超過(guò)10個(gè)時(shí),字符串的類(lèi)型才是__NSCFString
打印結(jié)果分析:
NSTaggedPointer標(biāo)志位
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
上面這個(gè)方法我們看到,判斷一個(gè)對(duì)象類(lèi)型是否為NSTaggedPointerString類(lèi)型實(shí)際上是講對(duì)象的地址與_OBJC_TAG_MASK進(jìn)行按位與操作,結(jié)果在跟_OBJC_TAG_MASK進(jìn)行對(duì)比,我們?cè)诳聪?code>_OBJC_TAG_MASK的定義:
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
我們都知道一個(gè)對(duì)象地址為64位二進(jìn)制,它表明如果64位數(shù)據(jù)中,最高位是1的話(huà),則表明當(dāng)前是一個(gè)tagged pointer類(lèi)型。
那么我們?cè)诳聪律厦娲蛴〕龅牡刂?,所?code>NSTaggedPointerString地址都是0xd開(kāi)頭,d轉(zhuǎn)換為二進(jìn)制1110,根據(jù)上面的結(jié)論,我們看到首位為1表示為NSTaggedPointerString類(lèi)型。在這里得到驗(yàn)證。
注意:TaggedPointer類(lèi)型在iOS和MacOS中標(biāo)志位是不同的iOS為最高位而MacOS為最低位
對(duì)象類(lèi)型
正常情況下一個(gè)對(duì)象的類(lèi)型,是通過(guò)這個(gè)對(duì)象的ISA指針來(lái)判斷的,那么對(duì)于NSTaggedPointer類(lèi)型我們?nèi)绾瓮ㄟ^(guò)地址判斷對(duì)應(yīng)數(shù)據(jù)是什么類(lèi)型的呢?
objc4-723之前
在objc4-723之前,我們可以通過(guò)與判斷TaggedPointer標(biāo)志位一樣根據(jù)地址來(lái)判斷,而類(lèi)型的標(biāo)志位就是對(duì)象地址的61-63位,比如對(duì)象地址為0xa開(kāi)頭,那么轉(zhuǎn)換成二進(jìn)制位1010,那么去掉最高位標(biāo)志位后,剩余為010,即10進(jìn)制中的2。
接著我們看下runtime源碼objc-internal.h中有關(guān)于標(biāo)志位的定義如下:
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
那么我們知道2表示的OBJC_TAG_NSString即字符串類(lèi)型。因?yàn)槟壳耙呀?jīng)無(wú)法驗(yàn)證這種情況了 所以我們不做其他類(lèi)型驗(yàn)證。
objc4-750之后
// Returns a pointer to the class's storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
uintptr_t obfuscatedTag = tag ^ tagObfuscator;
// Array index in objc_tag_classes includes the tagged bit itself
#if SUPPORT_MSB_TAGGED_POINTERS ////高位優(yōu)先
return &objc_tag_classes[0x8 | obfuscatedTag];
#else
return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}
classSlotForBasicTagIndex() 函數(shù)的主要功能就是根據(jù)指定索引 tag 從數(shù)組objc_tag_classes中獲取類(lèi)指針,而下標(biāo)的計(jì)算方法發(fā)是根據(jù)外部傳遞的索引tag。比如字符串 tag = 2。當(dāng)然這并不是簡(jiǎn)單的從數(shù)組中獲取某條數(shù)據(jù)。
uint16_t NSString_Tag = 2;
uint16_t NSNumber_Tag = 3;
// 3 = 0011
// _OBJC_TAG_INDEX_MASK = 0x7 = 0111
uintptr_t string_tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
uintptr_t number_tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
// 異或操作 相同返回0 不同返回1
// 2 ^ 3 = 0010 ^ 0011 = 0001
// 3^ 3 = 0011 ^ 0011 = 0000
uintptr_t string_obfuscatedTag = NSString_Tag ^ string_tagObfuscator;
uintptr_t number_obfuscatedTag = NSNumber_Tag ^ number_tagObfuscator;
// 按位或
// 1000 | 0001 = 1001 = 9
// 1000 | 0000 = 1000 = 8
NSLog(@"%@", objc_tag_classes[0x8 | string_obfuscatedTag]);
NSLog(@"%@", objc_tag_classes[0x8 | number_obfuscatedTag]);
控制臺(tái)輸出為:
TaggedPointer[89420:3027642] NSTaggedPointerString
TaggedPointer[89420:3027642] __NSCFNumber
當(dāng)我們多次運(yùn)行時(shí),我們發(fā)現(xiàn)實(shí)際上每次獲取到的string_tagObfuscator和number_obfuscatedTag都不一樣,但是每次從objc_tag_classes中取出的類(lèi)型均是一致的,因此實(shí)際上每次運(yùn)行objc_tag_classes中的內(nèi)容也是不斷變化的。
如果你想進(jìn)一步的了解可以參考Objective-C中偽指針Tagged Pointer
NSCFNumber
下面我們?cè)诳聪翹SNumber類(lèi)型
NSNumber *number1 = @(0x1);
NSNumber *number2 = @(0x20);
NSNumber *number3 = @(0x3F);
NSNumber *numberFFFF = @(0xFFFFFFFFFFEFE);
NSNumber *maxNum = @(MAXFLOAT);
NSLog(@"number1 pointer is %p class is %@", number1, number1.class);
NSLog(@"number2 pointer is %p class is %@", number2, number2.class);
NSLog(@"number3 pointer is %p class is %@", number3, number3.class);
NSLog(@"numberffff pointer is %p class is %@", numberFFFF, numberFFFF.class);
NSLog(@"maxNum pointer is %p class is %@", maxNum, maxNum.class);
我們?cè)诳聪麓蛴〗Y(jié)果:
TaggedPointerDemo[59218:2167895] number1 pointer is 0xf7cb914ffb51479a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] number2 pointer is 0xf7cb914ffb51458a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] number3 pointer is 0xf7cb914ffb51447a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] numberffff pointer is 0xf7346eb004aea86b class is __NSCFNumber
TaggedPointerDemo[59218:2167895] maxNum pointer is 0x28172a0c0 class is __NSCFNumber
我們發(fā)現(xiàn)對(duì)于NSNumber,我們打印出來(lái)的數(shù)據(jù)類(lèi)型均為__NSCFNumber,但是我們發(fā)現(xiàn)對(duì)于MAXFLOAT打印出的地址顯然與其他幾項(xiàng)不符,上面幾個(gè)NSNumber的地址以0xf開(kāi)頭,根據(jù)字符串地址的經(jīng)驗(yàn)我們可以看出f = 1111,首位標(biāo)記位為1,表示這個(gè)數(shù)據(jù)類(lèi)型屬于TaggedPointer。而MAXFLOAT不是。
獲取TaggedPointer的值
objc4-723之前
字符串:
[圖片上傳失敗...(image-e6401a-1625042343369)]
從上圖的地址中我們就可以看出,從低位到高位分別表示的就是字符串的值(在A(yíng)SCII碼表中的值)
數(shù)字:
[圖片上傳失敗...(image-1f04a-1625042343369)]
對(duì)于數(shù)字來(lái)說(shuō)從地址中也是直接讀出存儲(chǔ)的值,如上圖。
objc4-750之后
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{
// assert(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}
static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr)
{
// assert(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}
示例代碼:
NSString *str1 = [NSString stringWithFormat:@"1"];
NSString *str11 = [NSString stringWithFormat:@"11"];
NSString *str2 = [NSString stringWithFormat:@"2"];
NSString *str22 = [NSString stringWithFormat:@"22"];
// 0x31 1 0x32 1
uintptr_t value1 = objc_getTaggedPointerValue((__bridge void *)str1);
uintptr_t value2 = objc_getTaggedPointerValue((__bridge void *)str2);
uintptr_t value11 = objc_getTaggedPointerValue((__bridge void *)str11);
uintptr_t value22 = objc_getTaggedPointerValue((__bridge void *)str22);
// 以16進(jìn)制形式輸出
NSLog(@"%lx", value1);
NSLog(@"%lx", value11);
NSLog(@"%lx", value2);
NSLog(@"%lx", value22);
控制臺(tái)輸出:
TaggedPointer[89535:3033433] 311
TaggedPointer[89535:3033433] 31312
TaggedPointer[89535:3033433] 321
TaggedPointer[89535:3033433] 32322
即 "1" = 0x31 1,最后一位表示長(zhǎng)度,在A(yíng)SCII碼表中31表示的就是字符1。而且從字符串“11”的結(jié)果我們也可以驗(yàn)證上面的說(shuō)法。
isa 指針(NONPOINTER_ISA)
上面我們說(shuō)了,對(duì)于一個(gè)對(duì)象的存儲(chǔ),蘋(píng)果做了優(yōu)化,那么對(duì)于ISA指針呢?
對(duì)象的isa指針,用來(lái)表明對(duì)象所屬的類(lèi)類(lèi)型。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
同時(shí)結(jié)合下圖,我們可以更清晰的了解isa指針的作用以及類(lèi)對(duì)象的概念。

從圖中可以看出,我們所謂的isa指針,最后實(shí)際上落腳于isa_t的聯(lián)合類(lèi)型。那么何為聯(lián)合類(lèi)型呢? 聯(lián)合類(lèi)型是C語(yǔ)言中的一種類(lèi)型,是一種n選1的關(guān)系,聯(lián)合的作用在于,用更少的空間,表示了更多的可能的類(lèi)型,雖然這些類(lèi)型是不能夠共存的。比如isa_t 中包含有cls,bits, struct三個(gè)變量,它們的內(nèi)存空間是重疊的。在實(shí)際使用時(shí),僅能夠使用它們中的一種,你把它當(dāng)做cls,就不能當(dāng)bits訪(fǎng)問(wèn),你把它當(dāng)bits,就不能用cls來(lái)訪(fǎng)問(wèn)。
對(duì)于isa_t聯(lián)合類(lèi)型,主要包含了兩個(gè)構(gòu)造函數(shù)isa_t(),isa_t(uintptr_t value)和三個(gè)變量cls,bits,struct,而uintptr_t的定義為typedef unsigned long。
當(dāng)isa_t作為Class cls使用時(shí),這符合了我們之前一貫的認(rèn)知:isa是一個(gè)指向?qū)ο笏鶎貱lass類(lèi)型的指針。然而,僅讓一個(gè)64位的指針表示一個(gè)類(lèi)型,顯然不劃算。
因此,絕大多數(shù)情況下,蘋(píng)果采用了優(yōu)化的isa策略,即,isa_t類(lèi)型并不等同而Class cls, 而是struct。
struct
下面我們先來(lái)看下struct的結(jié)構(gòu)體
// ISA_BITFIELD定義如下
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
注意:成員后面的:表明了該成員占用幾個(gè)bit 而每個(gè)成員的意義如下表
標(biāo)志位說(shuō)明
| 成員 | bit位 | 說(shuō)明 | | --- | --- | --- | | nonpointer | 1bit | 標(biāo)志位。1(奇數(shù))表示開(kāi)啟了isa優(yōu)化,0(偶數(shù))表示沒(méi)有啟用isa優(yōu)化。所以,我們可以通過(guò)判斷isa是否為奇數(shù)來(lái)判斷對(duì)象是否啟用了isa優(yōu)化 | | has_assoc | 1bit | 標(biāo)志位。表明對(duì)象是否有關(guān)聯(lián)對(duì)象。沒(méi)有關(guān)聯(lián)對(duì)象的對(duì)象釋放的更快。 | | has_cxx_dtor | 1bit | 標(biāo)志位。表明對(duì)象是否有C++或ARC析構(gòu)函數(shù)。沒(méi)有析構(gòu)函數(shù)的對(duì)象釋放的更快| | shiftcls | 33bit | 類(lèi)指針的非零位。 | | magic | 6bit | 固定為0x1a,用于在調(diào)試時(shí)區(qū)分對(duì)象是否已經(jīng)初始化。 | | weakly_referenced | 1bit | 標(biāo)志位。用于表示該對(duì)象是否被別的對(duì)象弱引用。沒(méi)有被弱引用的對(duì)象釋放的更快。 | | deallocating | 1bit | 標(biāo)志位。用于表示該對(duì)象是否正在被釋放。 | | has_sidetable_rc | 1bit | 標(biāo)志位。用于標(biāo)識(shí)是否當(dāng)前的引用計(jì)數(shù)過(guò)大,無(wú)法在isa中存儲(chǔ),而需要借用sidetable來(lái)存儲(chǔ)。(這種情況大多不會(huì)發(fā)生) | | extra_rc | 19bit | 對(duì)象的引用計(jì)數(shù)減1。比如,一個(gè)object對(duì)象的引用計(jì)數(shù)為7,則此時(shí)extra_rc的值為6。 |
從上表我們發(fā)現(xiàn),extra_rc和has_sidetable_rc是和引用計(jì)數(shù)相關(guān)的標(biāo)志位,當(dāng)extra_rc 不夠用時(shí),還會(huì)借助sidetable來(lái)存儲(chǔ)計(jì)數(shù)值,這時(shí),has_sidetable_rc會(huì)被標(biāo)志為1。
接下來(lái)我們來(lái)驗(yàn)證下,這些標(biāo)志位是否真的如表中介紹那樣。
引用計(jì)數(shù)
我們先來(lái)看下面這段代碼
- (void)testisa {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"1\. obj isa_t = %p", *(void **)(__bridge void*)obj);
}
控制臺(tái)輸出結(jié)果
TaggedPointerDemo[59983:2185591] 1\. obj isa_t = 0x1a1f335beb1
我們將地址0x1a1f335beb1轉(zhuǎn)換過(guò)后:

我們看到這時(shí)候 對(duì)象是nonpointer開(kāi)啟了isa優(yōu)化,且當(dāng)前的引用計(jì)數(shù)器為 extra_rc = 0 + 1 = 1;
下面我們接著測(cè)試
NSObject *obj = [[NSObject alloc] init];
NSLog(@"1\. obj isa_t = %p", *(void **)(__bridge void*)obj);
_obj1 = obj;
NSObject *tmpObj = obj;
NSLog(@"2\. obj isa_t = %p", *(void **)(__bridge void*)obj);
控制臺(tái)輸出為
TaggedPointerDemo[63235:2266690] 1\. obj isa_t = 0x1a1f335beb1
TaggedPointerDemo[63235:2266690] 2\. obj isa_t = 0x41a1f335beb1
我們將地址0x41a1f335beb1轉(zhuǎn)換過(guò)后:
[圖片上傳失敗...(image-262798-1625042343368)]
我們看到這時(shí)候,我們將obj強(qiáng)引用之后,又實(shí)用了一個(gè)局部變量對(duì)其進(jìn)行引用,所以這時(shí)的引用計(jì)數(shù)應(yīng)該為2,當(dāng)然從圖中我們也可以驗(yàn)證這一點(diǎn)。
weakly_referenced
我們這次添加一個(gè)弱引用來(lái)驗(yàn)證
_weakRefObj = _obj1;
NSLog(@"3\. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
控制臺(tái)輸出為
TaggedPointerDemo[63235:2266690] 3\. obj isa_t = 0x45a1f335beb1
這時(shí)候我們僅僅通過(guò)地址進(jìn)行判斷 當(dāng)添加了_obj2 = _obj1后,地址變?yōu)?code>0x61a1f335beb1與之前地址0x41a1f335beb1對(duì)比

上圖我們可以看到weakly_referenced標(biāo)志位被置為1.表示這個(gè)對(duì)象有被弱引用。
has_assoc
然后我們?cè)谔砑右粋€(gè)關(guān)聯(lián)屬性
NSObject *attachObj = [[NSObject alloc] init];
objc_setAssociatedObject(_obj1, "attachKey", attachObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"4\. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
控制臺(tái)輸出為:
TaggedPointerDemo[63235:2266690] 4\. obj isa_t = 0x45a1f335beb3

從上圖中我們看到has_assoc標(biāo)志位被置為1.
總結(jié)
截止到這里,我們通過(guò)觀(guān)察NSTaggedPointer,相關(guān)標(biāo)志位我們基本了解了NSTaggedPointer是如何存儲(chǔ)數(shù)據(jù)以及標(biāo)志位的作用。