03 isa探究

iOS開發(fā)者一定知道每個(gè)實(shí)例對象都有一個(gè)isa指針徐伐,其中存儲著對象的類信息。今天我們就來探究下isa是如何保存類的信息的女嘲。
通過objc的源碼可以找到我們在調(diào)用alloc方法創(chuàng)建實(shí)例對象的時(shí)候有初始化isa的操作畜份,其初始化代碼如下

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

由上述代碼可知,isa其實(shí)是isa_t類型的結(jié)構(gòu)體欣尼,那么isa_t又是什么呢爆雹?

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

從isa_t的定義中引出了一個(gè)新的數(shù)據(jù)類型--聯(lián)合體。我們來看下聯(lián)合體和結(jié)構(gòu)體有什么區(qū)別:

結(jié)構(gòu)體

結(jié)構(gòu)體是把不同的數(shù)據(jù)組合成一個(gè)整體,其變量是共存的顶别,不管變量是否使用,在創(chuàng)建時(shí)都會(huì)分配內(nèi)存拒啰。

  • 優(yōu)點(diǎn)存儲容量大驯绎,包容性強(qiáng),且成員之間不會(huì)相互影響谋旦。
  • 缺點(diǎn):所有變量都會(huì)分配內(nèi)存剩失,內(nèi)存消耗較大
聯(lián)合體

聯(lián)合體也是由不同的數(shù)據(jù)類型組成册着,但其變量是互斥的拴孤,所有的成員共占一段內(nèi)存,而且聯(lián)合體采用了內(nèi)存覆蓋技術(shù)甲捏,同一時(shí)刻只能保存一個(gè)成員變量的值演熟,如果對新的成員賦值,就會(huì)將原來的成員的值覆蓋掉司顿。

  • 優(yōu)點(diǎn):所有成員共用一段內(nèi)存芒粹,使內(nèi)存的使用更為精細(xì)靈活,同時(shí)也節(jié)省了內(nèi)存空間大溜。
  • 缺點(diǎn)包容性差化漆。
    我們再回過來看isa_t的定義,其定義了兩個(gè)變量一個(gè)為bits钦奋,另一個(gè)為cls座云,同時(shí)如果定義了ISA_BITFIELD的情況下,定義了位域結(jié)構(gòu)體以便我們精確的操作isa_t中的不同位的數(shù)據(jù)付材。ISA_BITFIELD的定義根據(jù)不同的cpu進(jìn)行了不同的定義朦拖,定義的字段和內(nèi)容大同小異,只是不同的字段占用的位數(shù)量不一樣厌衔,本文僅以x86_64為例說明:
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

如上所示就是ISA_BITFIELD在x86_64下的定義贞谓,其中:

  • nonpointer:存儲于第0位,標(biāo)識是否為優(yōu)化isa標(biāo)志葵诈。0代表不是優(yōu)化的isa裸弦,此isa是一個(gè)純指向類或者原類的指針;1標(biāo)識是優(yōu)化的isa作喘,此isa中包含類信息理疙,對象的引用計(jì)數(shù)等。我們現(xiàn)在創(chuàng)建的對象中的isa基本上都是優(yōu)化后的isa泞坦。

  • has_assoc:存儲于第1位窖贤,關(guān)聯(lián)對象標(biāo)志位。對象含有或者曾經(jīng)含有關(guān)聯(lián)引用。0表示沒有赃梧;1表示有滤蝠。沒有關(guān)聯(lián)引用的對象可以更快的釋放內(nèi)存。

  • has_cxx_dtor:存儲于第3位授嘀,析構(gòu)函數(shù)標(biāo)志位物咳,如果有析構(gòu)函數(shù),則需進(jìn)行析構(gòu)邏輯蹄皱,如果沒有览闰,則可以更快速的釋放對象。

  • shiftcls:存儲于第3-46位巷折,存儲類的信息压鉴,這個(gè)是我們主要關(guān)注的內(nèi)容

  • magic:存儲于第47-52位锻拘,判斷對象是否初始化完成油吭,是調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間。

  • weakly_referenced:存儲于第53位署拟,標(biāo)識對象是否被指向或者曾經(jīng)被指向一個(gè)ARC的弱引用變量上鞠。如果沒有弱引用的對象可以更快的被釋放。

  • unused:存儲于第54位芯丧。

  • has_sidetable_rc:存儲于第55位芍阎,判斷該對象的引用計(jì)數(shù)是否過大,如果過大則需要其他散列表來進(jìn)行存儲缨恒。

  • extra_rc:存儲于第56-63位谴咸,存放該對象引用計(jì)數(shù)值-1的結(jié)果

    ISA_BITFIELD

了解了isa的數(shù)據(jù)類型之后我們看下objc是如何設(shè)置isa的呢骗露?根據(jù)objc源碼我們找到了設(shè)置isa的具體方法initIsa岭佳,代碼如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    isa = newisa;
}

在實(shí)際開發(fā)中,我們創(chuàng)建的類基本上都是nonpointer的萧锉,故此處isa的創(chuàng)建會(huì)走else中的代碼珊随,在此處打上斷點(diǎn)后我們一步一步跟斷點(diǎn)。


ISA_MAGIC_VALUE

經(jīng)過ISA_MAGIC_VALUE初始化newisa.bit之后柿隙,我們看到newisa中bits的值變?yōu)榱?303511812964353叶洞,而ISA_MAGIC_VALUE轉(zhuǎn)為10進(jìn)制數(shù)也是8303511812964353,在計(jì)算器中輸入后可以看到2進(jìn)制計(jì)數(shù)法中每一位的情況禀崖,如下所示

ISA_MAGIC_VALUE

newisa的位信息中衩辟,除了nonpointer為1與計(jì)算器中的第0位的1相符之外,magic被置為了59波附,同時(shí)計(jì)算器中從第47位開始(即magic的首位)顯示為110111艺晴,此時(shí)我們將59輸入計(jì)算器中發(fā)現(xiàn)59的二進(jìn)制計(jì)數(shù)正是110111昼钻,與bit中的值相等

59

下一步是setClass封寞,我們進(jìn)入setClass方法后發(fā)現(xiàn)設(shè)置shiftcls的代碼就一行然评。

    shiftcls = (uintptr_t)newCls >> 3;

此處將class右移了3位存入了shiftcls中,這是因?yàn)?code>class信息的最后4位始終是0狈究,即無效位挤巡,為了匹配shiftcls是從第3位開始存儲的钟病,便去除了最右邊三位無效位存入了shiftcls中授翻。

setClass

在setClass之后shiftcls的信息變成了536875037去扣,那么此時(shí)shiftcls中存儲的是否是SLPerson的類信息呢风喇?
我們通過以下幾種方式來看下:

  1. 我們剛剛說了class的最后三位是0宁改,所以我們只需要將isa中除了shiftcls的其它位置0便可以得到正確的類信息,即我們可以采取右移3位魂莫,左移20位还蹲,再右移17位的方式將其余位全部置0。
(lldb) p newisa.bits >> 3
(uintptr_t) $53 = 1037939513495581
(lldb) p $53 << 20
(uintptr_t) $54 = 562954278797312
(lldb) p $54 >> 17
(uintptr_t) $55 = 4295000296
(lldb) po $55
SLPerson
運(yùn)算過程

通過如上打印可以看到我們將其余位全部置0后得到的信息就是SLPerson耙考。
這是我們直接通過位運(yùn)算來還原了isa中類的信息谜喊,那么系統(tǒng)又是如何獲取類的信息的呢?

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}

inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;
    clsbits &= ISA_MASK;

    return (Class)clsbits;
#endif
}

#   define ISA_MASK        0x00007ffffffffff8ULL

從源碼可知倦始,獲取class的方法最終就是一行代碼return bits & ISA_MASK斗遏,我們將ISA_MASK放入計(jì)算器中發(fā)現(xiàn)就是一個(gè)3-46位為1,其余位為0的二進(jìn)制數(shù)鞋邑。與bits按位進(jìn)行&運(yùn)算最終的結(jié)果和我們進(jìn)行位移運(yùn)算是一樣的~

ISA_MASK

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诵次,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子枚碗,更是在濱河造成了極大的恐慌逾一,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肮雨,死亡現(xiàn)場離奇詭異遵堵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)怨规,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門陌宿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人波丰,你說我怎么就攤上這事限番。” “怎么了呀舔?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵弥虐,是天一觀的道長扩灯。 經(jīng)常有香客問我,道長霜瘪,這世上最難降的妖魔是什么珠插? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮颖对,結(jié)果婚禮上捻撑,老公的妹妹穿的比我還像新娘。我一直安慰自己缤底,他們只是感情好顾患,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著个唧,像睡著了一般江解。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徙歼,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天犁河,我揣著相機(jī)與錄音,去河邊找鬼魄梯。 笑死桨螺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酿秸。 我是一名探鬼主播灭翔,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辣苏!你這毒婦竟也來了肝箱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤考润,失蹤者是張志新(化名)和其女友劉穎狭园,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糊治,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唱矛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了井辜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绎谦。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖粥脚,靈堂內(nèi)的尸體忽然破棺而出窃肠,到底是詐尸還是另有隱情,我是刑警寧澤刷允,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布冤留,位于F島的核電站碧囊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏纤怒。R本人自食惡果不足惜糯而,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泊窘。 院中可真熱鬧熄驼,春花似錦、人聲如沸烘豹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽携悯。三九已至祭芦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚌卤,已是汗流浹背实束。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工奥秆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逊彭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓构订,卻偏偏與公主長得像侮叮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子悼瘾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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