iOS-底層探索03:isa底層結(jié)構(gòu)分析

iOS 底層探索 文章匯總

前言

在上一篇的文章iOS alloc 流程分析中我們分析了alloc杨凑,知道了alloc創(chuàng)建了對象并且分配內(nèi)存鸿染,同時初始化isa屬性。我們也知道了Objective-C 對象在底層本質(zhì)上是結(jié)構(gòu)體石洗,所有的對象里面都會包含有一個isa,這篇文章我們來分析探究isa底層是如何實(shí)現(xiàn)的

目錄


一锁荔、isa的初始化

  1. 源碼下載地址、配置源碼可編譯調(diào)試參考這里

  2. isa的初始化流程

//_class_createInstanceFromZone的部分代碼
//初始化實(shí)例的isa指針
 obj->initInstanceIsa(cls, hasCxxDtor);
 
 inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {// 上一個方法傳入的nonpointer為true![![![WX20200910-165320.png](https://upload-images.jianshu.io/upload_images/2987980-7afa02c886094fc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](https://upload-images.jianshu.io/upload_images/2987980-9e9fe9cabbde3a9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](https://upload-images.jianshu.io/upload_images/2987980-0feda4063f97c6de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
#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
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

其中的nonpointer字面的意思是沒有指針的蝙砌,一般情況下nonpointer是為true的阳堕,只有在例如實(shí)現(xiàn)了allocwithzone方法,retain择克,release等的時候會是false恬总。如果為false是直接將傳進(jìn)來的cls為isa的關(guān)聯(lián)的cls賦值。

其他的剩下的部分就是對isa的初始化賦值了肚邢。但是具體的isa內(nèi)部是怎樣的還是不知道的壹堰,從源碼中isa_t點(diǎn)擊進(jìn)去可以查看。

二骡湖、 isa的內(nèi)部結(jié)構(gòu)

通過源碼可以知道isaisa_t類型的內(nèi)部結(jié)構(gòu)

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
};

typedef unsigned long           uintptr_t;

從中可以知道贱纠,isa是一個聯(lián)合體 union,里面有關(guān)聯(lián)的類cls和long型的bits响蕴。

什么是聯(lián)合體(union)呢谆焊?聯(lián)合體是一種特殊的類,也是一種構(gòu)造類型的數(shù)據(jù)結(jié)構(gòu)浦夷。完全就是共用一個內(nèi)存首地址辖试,并且各種變量名都可以同時使用辜王,操作也是共同生效。所以也叫共用體剃执。并且聯(lián)合體(union)中是各變量是“互斥”的誓禁,但是內(nèi)存使用更為精細(xì)靈活,也節(jié)省了內(nèi)存空間肾档。

聯(lián)合體(union)摹恰、位域不清楚的可以參考這篇文章

由上面的概念可以知道,clsbits之間是互斥的怒见,即有cls就沒有bits,有bits就沒有cls俗慈。這就很好地解釋了為什么上面的源碼在初始化isa的時候會用nonpointer來區(qū)分開。所以isa的大小占8個字節(jié)遣耍,64位闺阱。其中這64位中分別存儲了什么呢?通過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)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      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 deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

這兩種是分別arm64x86系統(tǒng)架構(gòu)下isa的內(nèi)部結(jié)構(gòu)舵变,但是都是64位的酣溃。iPhone 采用arm64架構(gòu),MacOS 采用x86架構(gòu)纪隙,本文介紹x86架構(gòu)下isa的聯(lián)合體結(jié)構(gòu)赊豌。

  • nonpointer:占1位,表示是否對isa指針開啟指針優(yōu)化.
    0:不優(yōu)化绵咱,是純isa指針碘饼,當(dāng)訪問isa指針時,直接返回其成員變量cls
    1:優(yōu)化悲伶,即isa 指針內(nèi)容不止是類地址艾恼,還包含了類的一些信息、對象的引用計(jì)數(shù)等麸锉。
  • has_assoc:占1位钠绍,表示是否有關(guān)聯(lián)對象,0沒有花沉,1存在五慈。
  • has_cxx_dtor:占1位,表示該對象是否有C++或者Objc的析構(gòu)器主穗,如果有析構(gòu)函數(shù)泻拦,則需要做析構(gòu)邏輯,如果沒有則可以更快的釋放對象忽媒。
  • shiftcls:存儲類指針的值争拐。開啟指針優(yōu)化的情況下,在x86_64 架構(gòu)44位 用來存儲類指針地址,在arm64架構(gòu)中有33位用來存儲類指針地址。(由上面的初始化源碼也可以很好的說明,關(guān)聯(lián)的類指針向右移3位)
    newisa.shiftcls = (uintptr_t)cls >> 3;
  • magic:占6位架曹,用于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間隘冲。
  • weakly_referenced:占1位,表示對象是否被指向或者曾經(jīng)指向一個ARC的弱變量绑雄,沒有弱引用的對象可以更快釋放展辞。
  • deallocating:占1位,表示對象是否正在釋放內(nèi)存万牺。
  • has_sidetable_rc:占1位罗珍,表示當(dāng)對象引用技術(shù)大于10時,則需要借用該變量存儲進(jìn)位脚粟。
  • extra_rc:占8位覆旱,表示該對象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)總值減1核无。例如扣唱,如果對象的引用計(jì)數(shù)為10,那么extra_rc為9团南,如果引用計(jì)數(shù)大于10噪沙,則需要使用到上面的has_sidetable_rc

isa 在arm64架構(gòu)和x86架構(gòu)中的內(nèi)存布局

isa內(nèi)存布局

三吐根、isa是對象與類的連接

通過objc4-781蘋果官方的源碼正歼,使用object_getClass這個方法獲取到類。

NAPerson *objc = [NAPerson alloc];
Class testClass = object_getClass(objc);
 
NANSLog(@"%@",testClass);//NAPerson

通過源碼找到object_getClass的方法

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() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

define ISA_MASK        0x00007ffffffffff8ULL

從源碼中可以知道返回的isa最終是(Class)(isa.bits & ISA_MASK)
其中源碼有一個判斷isTaggedPointer(),其中蘋果對于Tagged Pointer的概念引入佑惠,是為了節(jié)省內(nèi)存和提高執(zhí)行效率,對于 64 位程序齐疙,相關(guān)邏輯能減少一半的內(nèi)存占用膜楷,以及 3 倍的訪問速度提升,100 倍的創(chuàng)建贞奋、銷毀速度提升赌厅。如果想了解這部分的內(nèi)容可以看看深入理解 Tagged Pointer

下面就是用lldb的指令來驗(yàn)證一下的。通過x/4gx objc打印出對象的內(nèi)存地址

WX20200910-162713.png

由源碼知道類Class的最終返回是(Class)(isa.bits & ISA_MASK)轿塔,所以將x/4gx objc打印出來的0x001d800100002119 & ISA_MASK的值得到如下:

WX20200910-163021.png

由前面的文章知道由于內(nèi)存的優(yōu)化對象的其他屬性的位置實(shí)際會發(fā)生變化的特愿,所以對象的第一個屬性就是 isa
通過測試我們發(fā)現(xiàn)對象的內(nèi)存地址和通過isa取出來的內(nèi)存地址是一樣的,所以isa是關(guān)聯(lián)著對象與類的

注意:對 shiftcls 的分析是建立在 nonpointer 為 1 的情況下勾缭,如果 nonpointer 為0揍障,整個 isa 存儲的是 cls 。

四俩由、isa的走位原理

通過上面的介紹毒嫡,可以知道了isa是關(guān)聯(lián)著對象與類的,并且對象的isa指向類幻梯,因?yàn)槿f物皆對象兜畸,那么類的isa指向的是誰呢努释?可以通過蘋果官方的isa的走位流程圖:

WX20200910-165320.png

其中虛線是代表isa的走位,實(shí)線代表的是繼承關(guān)系的走位咬摇。

通過以上的源碼分析伐蒂,我們認(rèn)識到對象的isa指針指向了對象所屬的類。而類本身也有一個isa指針肛鹏,它指向的又是什么呢逸邦?
此時要引入meta class(即元類)的概念了。我們先了解一下元類的信息:

OC中龄坪,對象的方法并沒有存儲于對象的結(jié)構(gòu)體中(如果每一個對象都保存了自己能執(zhí)行的方法昭雌,那么對內(nèi)存的占用有極大的影響)。

當(dāng)對象的實(shí)例方法被調(diào)用時健田,它通過自己的isa來查找對應(yīng)的類烛卧,然后在所屬類的 class_data_bits_t結(jié)構(gòu)體中查找對應(yīng)方法的實(shí)現(xiàn)。同時妓局,每一個objc_class也有一個指向自己的父類的指針superclass用來查找繼承的方法总放。

而當(dāng)調(diào)用 類方法 時,它的查找流程是怎樣的呢好爬?對此OC的解決方案就是引入元類局雄,來保證類方法也能通過相同的機(jī)制查找到。也就是說存炮,類的isa指向的是元類炬搭。

UIView實(shí)際上也是一個對象,叫做類對象穆桂,那么類對象的類又是什么呢宫盔?就是元類。元類也是一個對象享完,它的類又是什么呢灼芭?根元類。根元類也是一個對象般又,它的類又是什么呢彼绷?它自己
元類對象:OC類方法元類存在的根本原因茴迁。因?yàn)?code>元類對象中存儲著類對象調(diào)用的方法也就是類方法寄悯。元類的定義和創(chuàng)建都是編譯器自動完成的谐区,無需人為干涉同波,而且大部分時候都是傾向于隱藏的害晦。

驗(yàn)證過程見參考文章

isa指向結(jié)論
  • 對象的isa指針 指向 對象的所屬類(如person對象的isa指向Person類)

  • 類的isa指針指向 類的元類(如Person類的isa指向Person元類

  • 元類的isa指針指向 根元類(如Person元類isa指向NSObject元類

  • NSObject類的元類是根元類

  • NSObject元類isa指針指向自身(是個圓圈)

四、 isa總結(jié)

  1. isaisa_t結(jié)構(gòu)镣煮,采用 聯(lián)合體+位域 的搭配來設(shè)計(jì):在不同的位上顯示不同的內(nèi)容排苍,以此來節(jié)省儲存空間五芝,進(jìn)而優(yōu)化內(nèi)存猛频。
    2.isa包含了clsbits兩個成員變量,這兩個成員變量在64位CPU架構(gòu)下的長度都是8字節(jié)秘遏,所以isa64位CPU架構(gòu)下的長度也是8字節(jié)丘薛。
  2. isa的位域上存儲了一些對象與類的信息,并將對象與類關(guān)聯(lián)起來邦危,起到中間橋梁的作用洋侨。
  3. isa指向圖相關(guān)結(jié)論:
    • 對象的isa指針 指向 對象的所屬類(如person對象的isa指向Person類
    • 類的isa指針 指向 類的元類(如Person類的isa指向Person元類
    • 元類的isa指針 指向 根元類(如Person元類isa指向NSObject元類
      根元類的isa指針 指向自身(是個圓圈)
    • 元類的繼承關(guān)系向上傳遞(如Teacher元類 繼承自 Person元類
      根元類 繼承自根類
      根類 繼承自 nil

五、補(bǔ)充

5.1 如果不用ISA_MASK倦蚪,那么如何證明isa關(guān)聯(lián)了對象和類的作用呢希坚?

具體思路是,shiftclsx86_64架構(gòu)下長度是44位陵且,存儲在isa的 [3, 46]位上裁僧,所以可以通過將isa的 [0, 2]位、[47, 63]位清零慕购,同樣能得到shiftcls的值聊疲,進(jìn)而確定類。(先右移3位沪悲、再左移20位获洲、然后右移17位即可)

isa能保存保存很多數(shù)據(jù),但需要取出一個數(shù)據(jù)則需要將其他不相關(guān)位上置0殿如,然后整個64位的isa才能表示一個數(shù)據(jù)贡珊。

5.2 NSProxy的isa指向

如果Person類繼承的是NSProxy,相關(guān)isa指向是怎樣的呢涉馁?
答案:跟NSObject一樣门岔,兩者都是根類

參考文章

OC源碼分析之isa
iOS的OC的isa的底層原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谨胞,一起剝皮案震驚了整個濱河市固歪,隨后出現(xiàn)的幾起案子蒜鸡,更是在濱河造成了極大的恐慌胯努,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逢防,死亡現(xiàn)場離奇詭異叶沛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忘朝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門灰署,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事溉箕』耷剑” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵肴茄,是天一觀的道長晌畅。 經(jīng)常有香客問我,道長寡痰,這世上最難降的妖魔是什么抗楔? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拦坠,結(jié)果婚禮上连躏,老公的妹妹穿的比我還像新娘。我一直安慰自己贞滨,他們只是感情好入热,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疲迂,像睡著了一般才顿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤蒿,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天郑气,我揣著相機(jī)與錄音,去河邊找鬼腰池。 笑死尾组,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的示弓。 我是一名探鬼主播讳侨,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奏属!你這毒婦竟也來了跨跨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤囱皿,失蹤者是張志新(化名)和其女友劉穎勇婴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘱腥,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耕渴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了齿兔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橱脸。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡础米,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出添诉,到底是詐尸還是另有隱情屁桑,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布栏赴,位于F島的核電站掏颊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏艾帐。R本人自食惡果不足惜乌叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柒爸。 院中可真熱鬧准浴,春花似錦、人聲如沸捎稚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今野。三九已至葡公,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間条霜,已是汗流浹背催什。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宰睡,地道東北人蒲凶。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像拆内,于是被迫代替她去往敵國和親旋圆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344