內(nèi)存管理之Tagged pointer
iOS開發(fā)者對引用計數(shù)
這個名詞肯定不陌生,引用計數(shù)是蘋果為了方便開發(fā)者管理內(nèi)存而引入的一個概念,當(dāng)引用計數(shù)為0時樱拴,對象就會被釋放。但是稿湿,真的是所有對象都是這樣嗎?
內(nèi)存分配
iOS將虛擬內(nèi)存按照地址由低到高劃分為如下五個區(qū):
[圖片上傳失敗...(image-b07219-1625042343370)]
在程序運(yùn)行時,代碼區(qū)荆陆,常量區(qū)以及全局靜態(tài)區(qū)的大小是固定的篮愉,會變化的只有棧和堆的大小腐芍。而棧的內(nèi)存是有操作系統(tǒng)自動釋放的,我們平常說所的iOS內(nèi)存引用計數(shù)试躏,其實(shí)是就堆上的對象來說的猪勇。
如何引入tagged pointer
自2013年蘋果推出iphone5s之后,iOS的尋址空間擴(kuò)大到了64位颠蕴。我們可以用63位來表示一個數(shù)字(一位做符號位)泣刹。那么這個數(shù)字的范圍是2^63 ,很明顯我們一般不會用到這么大的數(shù)字助析,那么在我們定義一個數(shù)字時NSNumber *num = @100
,實(shí)際上內(nèi)存中浪費(fèi)了很多的內(nèi)存空間。
當(dāng)然蘋果肯定也認(rèn)識到了這個問題椅您,于是就引入了tagged pointer
,tagged pointer
是一種特殊的“指針”外冀,其特殊在于,其實(shí)它存儲的并不是地址掀泳,而是真實(shí)的數(shù)據(jù)和一些附加的信息雪隧。
我們可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋果對于Tagged Pointer特點(diǎn)的介紹:
- Tagged Pointer專門用來存儲小的對象员舵,例如NSNumber, NSDate, NSString膀跌。
- Tagged Pointer指針的值不再是地址了,而是真正的值固灵。所以捅伤,實(shí)際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已巫玻。所以丛忆,它的內(nèi)存并不存儲在堆中,也不需要malloc和free仍秤。
- 在內(nèi)存讀取上有著3倍的效率熄诡,創(chuàng)建時比以前快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)字符串的長度為10個以內(nèi)時凰浮,字符串的類型都是NSTaggedPointerString
類型,當(dāng)超過10個時苇本,字符串的類型才是__NSCFString
打印結(jié)果分析:
NSTaggedPointer標(biāo)志位
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
上面這個方法我們看到袜茧,判斷一個對象類型是否為NSTaggedPointerString
類型實(shí)際上是講對象的地址與_OBJC_TAG_MASK
進(jìn)行按位與操作,結(jié)果在跟_OBJC_TAG_MASK
進(jìn)行對比,我們在看下_OBJC_TAG_MASK
的定義:
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
我們都知道一個對象地址為64位二進(jìn)制,它表明如果64位數(shù)據(jù)中瓣窄,最高位是1的話笛厦,則表明當(dāng)前是一個tagged pointer
類型。
那么我們在看下上面打印出的地址俺夕,所有NSTaggedPointerString
地址都是0xd
開頭裳凸,d轉(zhuǎn)換為二進(jìn)制1110
,根據(jù)上面的結(jié)論劝贸,我們看到首位為1表示為NSTaggedPointerString
類型姨谷。在這里得到驗(yàn)證。
注意
:TaggedPointer
類型在iOS和MacOS中標(biāo)志位是不同的iOS為最高位而MacOS為最低位
對象類型
正常情況下一個對象的類型映九,是通過這個對象的ISA指針來判斷的梦湘,那么對于NSTaggedPointer
類型我們?nèi)绾瓮ㄟ^地址判斷對應(yīng)數(shù)據(jù)是什么類型的呢?
objc4-723之前
在objc4-723之前,我們可以通過與判斷TaggedPointer
標(biāo)志位一樣根據(jù)地址來判斷践叠,而類型的標(biāo)志位就是對象地址的61-63位,比如對象地址為0xa
開頭言缤,那么轉(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
即字符串類型管挟。因?yàn)槟壳耙呀?jīng)無法驗(yàn)證這種情況了 所以我們不做其他類型驗(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中獲取類指針,而下標(biāo)的計算方法發(fā)是根據(jù)外部傳遞的索引tag弄捕。比如字符串 tag = 2僻孝。當(dāng)然這并不是簡單的從數(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]);
控制臺輸出為:
TaggedPointer[89420:3027642] NSTaggedPointerString
TaggedPointer[89420:3027642] __NSCFNumber
當(dāng)我們多次運(yùn)行時,我們發(fā)現(xiàn)實(shí)際上每次獲取到的string_tagObfuscator
和number_obfuscatedTag
都不一樣守谓,但是每次從objc_tag_classes
中取出的類型均是一致的穿铆,因此實(shí)際上每次運(yùn)行objc_tag_classes中的內(nèi)容也是不斷變化的。
如果你想進(jìn)一步的了解可以參考Objective-C中偽指針Tagged Pointer
NSCFNumber
下面我們在看下NSNumber類型
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);
我們在看下打印結(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)對于NSNumber斋荞,我們打印出來的數(shù)據(jù)類型均為__NSCFNumber
,但是我們發(fā)現(xiàn)對于MAXFLOAT
打印出的地址顯然與其他幾項(xiàng)不符荞雏,上面幾個NSNumber的地址以0xf
開頭,根據(jù)字符串地址的經(jīng)驗(yàn)我們可以看出f = 1111
,首位標(biāo)記位為1平酿,表示這個數(shù)據(jù)類型屬于TaggedPointer
凤优。而MAXFLOAT不是。
獲取TaggedPointer的值
objc4-723之前
字符串:
[圖片上傳失敗...(image-e6401a-1625042343369)]
從上圖的地址中我們就可以看出,從低位到高位分別表示的就是字符串的值(在ASCII碼表中的值)
數(shù)字:
[圖片上傳失敗...(image-1f04a-1625042343369)]
對于數(shù)字來說從地址中也是直接讀出存儲的值蜈彼,如上圖筑辨。
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);
控制臺輸出:
TaggedPointer[89535:3033433] 311
TaggedPointer[89535:3033433] 31312
TaggedPointer[89535:3033433] 321
TaggedPointer[89535:3033433] 32322
即 "1" = 0x31 1,最后一位表示長度,在ASCII碼表中31表示的就是字符1幸逆。而且從字符串“11”的結(jié)果我們也可以驗(yàn)證上面的說法棍辕。
isa 指針(NONPOINTER_ISA)
上面我們說了,對于一個對象的存儲还绘,蘋果做了優(yōu)化楚昭,那么對于ISA指針呢?
對象的isa指針蚕甥,用來表明對象所屬的類類型哪替。
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
};
同時結(jié)合下圖,我們可以更清晰的了解isa指針的作用以及類對象的概念菇怀。
從圖中可以看出,我們所謂的isa指針晌块,最后實(shí)際上落腳于isa_t的聯(lián)合類型爱沟。那么何為聯(lián)合類型呢? 聯(lián)合類型是C語言中的一種類型匆背,是一種n選1的關(guān)系,聯(lián)合的作用在于呼伸,用更少的空間,表示了更多的可能的類型,雖然這些類型是不能夠共存的
括享。比如isa_t 中包含有cls
搂根,bits
, struct
三個變量铃辖,它們的內(nèi)存空間是重疊的剩愧。在實(shí)際使用時,僅能夠使用它們中的一種娇斩,你把它當(dāng)做cls仁卷,就不能當(dāng)bits訪問,你把它當(dāng)bits犬第,就不能用cls來訪問锦积。
對于isa_t
聯(lián)合類型,主要包含了兩個構(gòu)造函數(shù)isa_t()
,isa_t(uintptr_t value)
和三個變量cls
,bits
,struct
,而uintptr_t
的定義為typedef unsigned long
歉嗓。
當(dāng)isa_t作為Class cls使用時丰介,這符合了我們之前一貫的認(rèn)知:isa是一個指向?qū)ο笏鶎貱lass類型的指針。然而鉴分,僅讓一個64位的指針表示一個類型哮幢,顯然不劃算。
因此冠场,絕大多數(shù)情況下家浇,蘋果采用了優(yōu)化的isa策略
,即碴裙,isa_t
類型并不等同而Class cls
, 而是struct
钢悲。
struct
下面我們先來看下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)
注意:成員后面的:表明了該成員占用幾個bit
而每個成員的意義如下表
標(biāo)志位說明
| 成員 | bit位 | 說明 | | --- | --- | --- | | nonpointer | 1bit | 標(biāo)志位。1(奇數(shù))表示開啟了isa優(yōu)化舔株,0(偶數(shù))表示沒有啟用isa優(yōu)化莺琳。所以,我們可以通過判斷isa是否為奇數(shù)來判斷對象是否啟用了isa優(yōu)化 | | has_assoc | 1bit | 標(biāo)志位载慈。表明對象是否有關(guān)聯(lián)對象惭等。沒有關(guān)聯(lián)對象的對象釋放的更快。 | | has_cxx_dtor | 1bit | 標(biāo)志位办铡。表明對象是否有C++或ARC析構(gòu)函數(shù)辞做。沒有析構(gòu)函數(shù)的對象釋放的更快| | shiftcls | 33bit | 類指針的非零位。 | | magic | 6bit | 固定為0x1a寡具,用于在調(diào)試時區(qū)分對象是否已經(jīng)初始化秤茅。 | | weakly_referenced | 1bit | 標(biāo)志位。用于表示該對象是否被別的對象弱引用童叠。沒有被弱引用的對象釋放的更快框喳。 | | deallocating | 1bit | 標(biāo)志位。用于表示該對象是否正在被釋放。 | | has_sidetable_rc | 1bit | 標(biāo)志位五垮。用于標(biāo)識是否當(dāng)前的引用計數(shù)過大乍惊,無法在isa中存儲,而需要借用sidetable來存儲放仗。(這種情況大多不會發(fā)生) | | extra_rc | 19bit | 對象的引用計數(shù)減1润绎。比如,一個object對象的引用計數(shù)為7匙监,則此時extra_rc的值為6凡橱。 |
從上表我們發(fā)現(xiàn),extra_rc
和has_sidetable_rc
是和引用計數(shù)相關(guān)的標(biāo)志位亭姥,當(dāng)extra_rc 不夠用時稼钩,還會借助sidetable來存儲計數(shù)值,這時达罗,has_sidetable_rc會被標(biāo)志為1坝撑。
接下來我們來驗(yàn)證下,這些標(biāo)志位是否真的如表中介紹那樣粮揉。
引用計數(shù)
我們先來看下面這段代碼
- (void)testisa {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"1\. obj isa_t = %p", *(void **)(__bridge void*)obj);
}
控制臺輸出結(jié)果
TaggedPointerDemo[59983:2185591] 1\. obj isa_t = 0x1a1f335beb1
我們將地址0x1a1f335beb1
轉(zhuǎn)換過后:
我們看到這時候 對象是nonpointer
開啟了isa優(yōu)化巡李,且當(dāng)前的引用計數(shù)器為 extra_rc = 0 + 1 = 1;
下面我們接著測試
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);
控制臺輸出為
TaggedPointerDemo[63235:2266690] 1\. obj isa_t = 0x1a1f335beb1
TaggedPointerDemo[63235:2266690] 2\. obj isa_t = 0x41a1f335beb1
我們將地址0x41a1f335beb1
轉(zhuǎn)換過后:
[圖片上傳失敗...(image-262798-1625042343368)]
我們看到這時候,我們將obj強(qiáng)引用之后扶认,又實(shí)用了一個局部變量對其進(jìn)行引用侨拦,所以這時的引用計數(shù)應(yīng)該為2,當(dāng)然從圖中我們也可以驗(yàn)證這一點(diǎn)辐宾。
weakly_referenced
我們這次添加一個弱引用來驗(yàn)證
_weakRefObj = _obj1;
NSLog(@"3\. obj isa_t = %p", *(void **)(__bridge void*)_obj1);
控制臺輸出為
TaggedPointerDemo[63235:2266690] 3\. obj isa_t = 0x45a1f335beb1
這時候我們僅僅通過地址進(jìn)行判斷 當(dāng)添加了_obj2 = _obj1
后狱从,地址變?yōu)?code>0x61a1f335beb1與之前地址0x41a1f335beb1
對比
上圖我們可以看到weakly_referenced
標(biāo)志位被置為1.表示這個對象有被弱引用。
has_assoc
然后我們在添加一個關(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);
控制臺輸出為:
TaggedPointerDemo[63235:2266690] 4\. obj isa_t = 0x45a1f335beb3
從上圖中我們看到has_assoc
標(biāo)志位被置為1.
總結(jié)
截止到這里叠纹,我們通過觀察NSTaggedPointer
,相關(guān)標(biāo)志位我們基本了解了NSTaggedPointer是如何存儲數(shù)據(jù)以及標(biāo)志位的作用季研。