內存管理方案
- TaggedPointer
- NONPOINTER_ISA
一、TaggedPointer
2020年WWDC【本】老頭講的關于底層的改變
在Intel架構上,最后一位表示Tagged pointers標志位,最后接下來的三位代表Tag數據類型,當Tag的值是小于等于6( <= 6)
時,有效負載Payload是60
位,其代表的 7 種數據類型是:
// 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,
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_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
如果Tag=7,則接下來的8位擴展標簽Extended代表類型(這就有了2^8=256種可表示類型),如UIColor
和NSIndexSet
等,此時有效負載Payload就只有52
位。
在ARM64(iOS13之前(含13))架構上正好反過來,第一位表示Tagged pointers標志位,接下來的三位代表Tag數據類型以此類推,剩下的和Intel架構一樣。但為什么ARM64上要這么做呢?主要是考慮objc_msgSend上的優化,這樣就可以使得msgSend中最常見的路徑盡可能的快,實際上就是放在前面,可以在msgSend消息發送路徑上判斷少了,可以判斷Tagged或nil的情況,不用分開判斷,減少一個分支。
但是在ARM架構的iOS14以后(含14)這這些位又發生了一些變化,Tag還是放到了第三位,因為根據字節對齊的規則,這三位總是0,所以可以利用這三位。Extended在Tagged pointers后面的高8位,這樣做的原因是因為ARM的Top Bite lgnore特性,使得會忽略指針的錢8位,所以可以利用這個特性。使用了Tagged pointers使得一些小數據的存儲不用存在dirty memory,而是存在clean memory。
當字符串長度小等于7的時候,每次運行的結果都是一樣的,所以當長度小于7的時候,其字符串的值直接存在Payload中,根據相應架構和系統版本,可以打印如下:
當字符串長度大于7是,其表現形式是怎樣的呢?嘗試了長度位9的情況,其類型還是NSTaggedPointerString
類型,而且每次運行的值是一樣的,說明Payload不是地址也是值得形式,但是不想7位長度的時候需要8位二進制表示,此時是6位二進制表示字符編碼,其對應的字符串范圍是如下表:
字符串范圍 | 小于8 表示類型 | [8,10)表示類型 | [10,11]表示類型 | 大于11表示類型 |
---|---|---|---|---|
eilotrm.apdnslc ufkMShjTRxgC4013 | TaggedPointer | TaggedPointer | TaggedPointer | OC對象 |
eilotrm.apdnslc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX | TaggedPointer | TaggedPointer | OC對象 | OC對象 |
ASCII碼 | TaggedPointer | OC對象 | OC對象 | OC對象 |
NSNumber
小數值也是直接存在Payload中,并沒有也不需要malloc和free堆內存。這樣做的好處使得讀取速度快了3倍,創建速度快了100多倍。
且對比了一下tag前面4位的值:
Char
? 是 0000
-- 0
Short
?是 0001
-- 1
Int
?? 是 0010
-- 2
Long
??是 0011
-- 3
當然可以禁用Tagged Pointers,通過添加環境變量OBJC_DISABLE_TAGGED_POINTERS
在前面的復選框內打鉤并在后面的value設置位YES就disable,當然在特定的架構系統下,運行在發送objcMsgSend的時候就crash,是因為在底層Tagged pointers是必須的,否則斷言報錯。
源碼
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
// PAYLOAD_LSHIFT 和 PAYLOAD_RSHIFT 是一些宏擴展,根據不同的架構值不一樣.
// OBJC_TAG_Last60BitPayload = 6
if (tag <= OBJC_TAG_Last60BitPayload) { // tag<=6時 makeTaggedPointer ,Payload是60位
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
} else { // 52位Payload
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
// 編碼過程
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS // 如果支持Tagged pointers ,以下分析是M1電腦的
// _OBJC_TAG_NO_OBFUSCATION_MASK代表最高兩位為1,最后三位也為1,其他59位都是0
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return (void *)ptr; // 如果已經encode過直接返回
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; // 將低3位置1,basicTag值是7
uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag); // permutedTag 值為7
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT); // 將value的值保留,并將最低3位置0
value |= permutedTag << _OBJC_TAG_INDEX_SHIFT; // 將value上面的低三位置1
#endif
return (void *)value;
}
// 解碼過程
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
}
二、NONPOINTER_ISA
??在【 iOS底層探索--isa位域 】 這篇文章中我們可以知道對象的isa指針不僅僅是指向了類的地址,因為64位二進制,如果僅僅表現類的指針,而在Runtime的運行機制、msgSend消息發送機制、消息轉發機制、對象類的內存管理機制中需要很多額外的輔助標記才能提高效率,所以如果額外定義難免浪費不必要的內存,所以利用isa的64位中非shiftcls
的位用于標志諸如,關聯對象標志,C++析構器標志,弱引用,是否正在銷毀,引用計數表等等,可以提高效率的同時節省了很多內存開銷。
可通過修改環境變量OBJC_DISABLE_NONPOINTER_ISA = YES
得到一個純的ISA數據。