objc使用TaggedPointer的原因
TaggedPointer是一個能夠提升性能, 節(jié)省內(nèi)存的技術(shù). NSString, NSNumber中就采用了這個技術(shù).
對象在內(nèi)存中都是對齊的, 它們的地址總是指針大小的整數(shù)倍, 通常是16的倍數(shù). 在64位系統(tǒng)中, 對象指針是一個64位的整數(shù), 為了對齊, 對象指針的一些位永遠是0.
舉一個例子, 假設(shè)要存儲一個NSNumber對象
,其值是一個整數(shù). 正常情況下, 如果這個整數(shù)只是一個NSInteger的普通變量, 那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān), 在32位CPU下占4個字節(jié), 在64位CPU下是占8個字節(jié)的. 而指針類型的大小通常也是與CPU位數(shù)相關(guān), 一個指針所占用的內(nèi)存在32位CPU下為4個字節(jié), 在64位CPU下也是8個字節(jié). 所以一個普通的iOS程序, 如果沒有TaggedPointer
對象, 從32位機器遷移到64位機器中后, 雖然邏輯沒有任何變化, 但這種NSNumber、NSDate一類的對象所占用的內(nèi)存會翻倍焚碌。
與此同時, 在沒有使用TaggedPointer
之前, NSNumber等對象需要動態(tài)分配內(nèi)存、維護引用計數(shù)等届吁,NSNumber指針存儲的是堆中NSNumber對象的地址值.
為了改進上面提到的內(nèi)存占用和效率問題, 蘋果提出了TaggedPointer
對象. 由于NSNumber, NSDate, NSString等類的變量本身的值需要占用的內(nèi)存大小常常不需要8個字節(jié), 拿整數(shù)來說, 4個字節(jié)所能表示的有符號整數(shù)就可以達到20多億(注:2^31=2147483648错妖,另外1位作為符號位), 對于絕大多數(shù)情況都是可以處理的.
在使用TaggedPointer
之后, NSNumber指針里面存儲的數(shù)據(jù)變成了: Tag + Data.
因此可以說使用了TaggedPointer
以后, 對象指針是一個偽指針
, 不再指向?qū)ο笤诙焉系牡刂? 也就是將數(shù)據(jù)直接存儲在了指針中, 當指針不夠存儲數(shù)據(jù)時, 才會使用動態(tài)分配內(nèi)存的方式來存儲數(shù)據(jù).
如何判斷一個對象指針是否是TaggedPointer
我們通過objc判斷該對象指針是否是TaggedPointer
, 通過查看對象指針的tag bit
是否為1來判斷, 源碼如下:
//如果是iOS平臺, 最高有效位是1(第64bit, 使用高位優(yōu)先規(guī)則 MSB)
#define _OBJC_TAG_MASK (1UL<<63)
//如果是mac平臺, 最低有效位是1(低位優(yōu)先規(guī)則 LSB)
#define _OBJC_TAG_MASK 1UL
// objc-object.h 中
inline bool objc_object::isTaggedPointer() {
return _objc_isTaggedPointer(this);
}
// objc-internal.h 中
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
結(jié)論: 使用對象指針的最高位bit或者最低位bit來標識該指針是否是Tagged Pointer
.
為什么可以通過設(shè)定最高位或者最低位是否為 1 來標識呢?
因為在對象指針變量分配內(nèi)存的時候, 都是按2的整數(shù)倍來分配的, 這樣分配出來的正常內(nèi)存地址末位不可能為1, 這樣通過將最低標識為 1, 就可以和其他正常指針做出區(qū)分.
那么為什么最高位為 1, 也可以標識呢?
這是因為64位操作系統(tǒng), 設(shè)備一般沒有那么大的內(nèi)存, 所以內(nèi)存地址一般只有 48 個左右有效位, 也就是說高位的 16 位左右都為 0, 所以可以通過最高位標識為 1 來表示TaggedPointer
. 那么既然一位就可以標識TaggedPointer
了其他的信息是干嘛的呢? 我們可以想象, 要有一些bit位來表示這個指針對應(yīng)的類型, 不然拿到一個TaggedPointer
的時候我們不知道類型, 就無法解析成對應(yīng)的值疚沐。
從源碼中我們可以有如下注釋, 我進行了翻譯:
/***********************************************************************
* Tagged pointer objects. - 對于 TaggedPointer 對象的描述
*
* Tagged pointer objects store the class and the object value in the
* object pointer; the "pointer" does not actually point to anything.
* TaggedPointer對象的內(nèi)容, 是將對象指針(object pointer)用來保存類(class)和對象值(object value)!!! `pointer`指針并沒有指向任何對象(obejct), 我們稱這個為偽指針!!!
*
* Tagged pointer objects currently use this representation:
* (LSB) 低bit
* 1 bit set if tagged, clear if ordinary object pointer
* 3 bits tag index -- 用來表示 object的class
* 60 bits payload -- 這個負載是 object的class定義的內(nèi)容(NSString, NSNumber定義的內(nèi)容不同)
* (MSB) 高bit
* The tag index defines the object's class.
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an
* "extended" representation, allowing more classes but with smaller payloads:
* 如果tagindex的內(nèi)容是`ob111`, 這個`TaggedPointer`對象使用的是`extended`擴展描述法, 這個偽指針可以支持更多的類, 但是對應(yīng)的用來描述object值的bits減少成52bits
* (LSB) -- 低bit
* 1 bit set if tagged, clear if ordinary object pointer - 用來展示當前對象指針是taggedPointer還是普通對象指針!!!
* 3 bits 0b111 -- 描述是否是 extended tagged pointer
* 8 bits extended tag index -- 擴展 tag index
* 52 bits payload -- 具體的 objct值
* (MSB) -- 高bit
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/
總而言之:
- 低地址中最低的1bit, 標志當前對象指針(object pointer)是否是
TaggedPointer
. - 接著3bit位(轉(zhuǎn)化成10進制是 0~7), 標志當前對象是哪個類的內(nèi)容, 具體標志的內(nèi), 通過下面的枚舉能看到, 常用的是
OBJC_TAG_NSString
和OBJC_TAG_NSNumber
, 分別是2和3. - 如果
tagged index == 0b111
, 標志extended tag index
. - 其余的60bit 或者 52bit 就用來存儲具體的 object value.(如果tag index對應(yīng)是NSString, object value 是經(jīng)過編碼的, 參考文章中有具體的編碼方法)
這樣, 通過TaggedPointer
這種偽指針
方法, 一次性將class - objectValue放在對象指針中, 即節(jié)省內(nèi)存, 又提高了效率. 當然如果60bit/52bit還不能滿足object value的范圍, objc仍然會把它當做普通對象指針處理!!!
// 具體 tag index 與類關(guān)系如下
enum {
// 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,
...
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
TaggedPointer使用的實例
可以參考文獻, 其中有TaggedPointer
在NSNumber
和NSString
中的實踐應(yīng)用.
注意NSString到最后使用成為: NSTaggedPointerString
和 __NSCFString
參考
1 [譯]采用Tagged Pointer的字符串
2 深入理解Tagged Pointer
3 內(nèi)存管理-Tagged Pointer