objc 中的Tagged Pointer的應(yīng)用

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.
**********************************************************************/

總而言之:

  1. 低地址中最低的1bit, 標志當前對象指針(object pointer)是否是TaggedPointer.
  2. 接著3bit位(轉(zhuǎn)化成10進制是 0~7), 標志當前對象是哪個類的內(nèi)容, 具體標志的內(nèi), 通過下面的枚舉能看到, 常用的是OBJC_TAG_NSStringOBJC_TAG_NSNumber, 分別是2和3.
  3. 如果tagged index == 0b111, 標志extended tag index.
  4. 其余的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使用的實例

可以參考文獻, 其中有TaggedPointerNSNumberNSString中的實踐應(yīng)用.

注意NSString到最后使用成為: NSTaggedPointerString__NSCFString

參考

1 [譯]采用Tagged Pointer的字符串
2 深入理解Tagged Pointer
3 內(nèi)存管理-Tagged Pointer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暂氯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亮蛔,更是在濱河造成了極大的恐慌痴施,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件究流,死亡現(xiàn)場離奇詭異辣吃,居然都是意外死亡,警方通過查閱死者的電腦和手機芬探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門神得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偷仿,你說我怎么就攤上這事哩簿。” “怎么了酝静?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵卡骂,是天一觀的道長。 經(jīng)常有香客問我形入,道長,這世上最難降的妖魔是什么缝左? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任亿遂,我火速辦了婚禮,結(jié)果婚禮上渺杉,老公的妹妹穿的比我還像新娘蛇数。我一直安慰自己,他們只是感情好是越,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布耳舅。 她就那樣靜靜地躺著,像睡著了一般倚评。 火紅的嫁衣襯著肌膚如雪浦徊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天天梧,我揣著相機與錄音盔性,去河邊找鬼。 笑死呢岗,一個胖子當著我的面吹牛冕香,可吹牛的內(nèi)容都是我干的蛹尝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼悉尾,長吁一口氣:“原來是場噩夢啊……” “哼突那!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起构眯,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤愕难,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鸵赖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體务漩,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年它褪,在試婚紗的時候發(fā)現(xiàn)自己被綠了饵骨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡茫打,死狀恐怖居触,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情老赤,我是刑警寧澤轮洋,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站抬旺,受9級特大地震影響弊予,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜开财,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一汉柒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧责鳍,春花似錦碾褂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至恤溶,卻和暖如春乓诽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咒程。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工问裕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孵坚。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓粮宛,卻偏偏與公主長得像窥淆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巍杈,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉忧饭,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,725評論 0 9
  • 閱讀本文后你將會進一步了解Runtime的實現(xiàn),享元設(shè)計模式的實踐筷畦,內(nèi)存數(shù)據(jù)存儲優(yōu)化词裤,編譯內(nèi)存屏障,多線程無鎖讀寫...
    歐陽大哥2013閱讀 17,705評論 19 124
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,111評論 1 32
  • 本文基于objc4-709源碼進行分析鳖宾。關(guān)于源碼編譯:objc - 編譯Runtime源碼objc4-706 ob...
    WeiHing閱讀 827評論 1 3
  • 使用Object.defineProperty方法吼砂,然后通過get和set方法設(shè)置對象屬性,可以讓我們知道對象什么...
    放風(fēng)箏的小小馬閱讀 158評論 0 0