iOS底層探索之isa

一陨献、前置知識(shí)

1.1 C 共用體 || 聯(lián)合體

共用體是一種特殊的數(shù)據(jù)類(lèi)型,允許您在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類(lèi)型纽匙。您可以定義一個(gè)帶有多成員的共用體厌丑,但是任何時(shí)候只能有一個(gè)成員帶有值。共用體提供了一種使用相同的內(nèi)存位置的有效方式匹耕。

定義
為了定義結(jié)構(gòu)體聚请,您必須使用 union 語(yǔ)句,方式與定義結(jié)構(gòu)類(lèi)似。union 語(yǔ)句定義了一個(gè)新的數(shù)據(jù)類(lèi)型驶赏,帶有多個(gè)成員炸卑。union 語(yǔ)句的格式如下:

 union [union tag]
    {
       member definition;
       member definition;
       ...
       member definition;
    } [one or more union variables];
// [one or more union variables] 是可選的
--
eg:
  union uData {
    int age;        // 4字節(jié)
    long height;    // 8字節(jié)
    char sex;       // 1字節(jié)
  };

現(xiàn)在,Data 類(lèi)型的變量可以存儲(chǔ)一個(gè)整數(shù)煤傍、一個(gè)浮點(diǎn)數(shù)盖文,或者一個(gè)字符串。這意味著一個(gè)變量(相同的內(nèi)存位置)可以存儲(chǔ)多個(gè)多種類(lèi)型的數(shù)據(jù)蚯姆。您可以根據(jù)需要在一個(gè)共用體內(nèi)使用任何內(nèi)置的或者用戶(hù)自定義的數(shù)據(jù)類(lèi)型五续。

共用體占用的內(nèi)存應(yīng)足夠存儲(chǔ)共用體中最大的成員。例如龄恋,在上面的實(shí)例中疙驾,Data 將占用 8 個(gè)字節(jié)的內(nèi)存空間,因?yàn)樵诟鱾€(gè)成員中郭毕,height 所占用的空間是最大的它碎。

訪(fǎng)問(wèn)共用體成員

        union uData u = {};
        u.height = 100;
        NSLog(@"u: %lu", sizeof(u));
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex);
        u.age = 25;
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex);
        u.sex = 'f';
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex); 

---
age:100, height: 100, age: 100
age:25, height: 25, age: 25
age:102, height: 102, age: 102

可驗(yàn)證最大空間為公用體中,最大的成員所占空間铣卡。也可知成員變量是訪(fǎng)問(wèn)的同一片內(nèi)存空間链韭。

1.2 C 位域

如果程序的結(jié)構(gòu)中包含多個(gè)開(kāi)關(guān)量,只有 TRUE/FALSE 變量煮落,如下:

    struct
    {
      unsigned int widthValidated;
      unsigned int heightValidated;
    } status;

這種結(jié)構(gòu)需要 8 字節(jié)的內(nèi)存空間敞峭,但在實(shí)際上,在每個(gè)變量中蝉仇,我們只存儲(chǔ) 0 或 1旋讹。在這種情況下,C 語(yǔ)言提供了一中更好的利用內(nèi)存空間的方式轿衔。如果您在結(jié)構(gòu)內(nèi)使用這樣的變量沉迹,您可以定義變量的寬度來(lái)告訴編譯器,您將只使用這些字節(jié)害驹。例如鞭呕,上面的結(jié)構(gòu)可以重寫(xiě)成:

   struct
    {
      unsigned int widthValidated : 1;
      unsigned int heightValidated : 1;
    } status;

現(xiàn)在,上面的結(jié)構(gòu)中宛官,status 變量將占用 4 個(gè)字節(jié)的內(nèi)存空間葫松,但是只有 2 位被用來(lái)存儲(chǔ)值。如果您用了 32 個(gè)變量底洗,每一個(gè)變量寬度為 1 位腋么,那么 status 結(jié)構(gòu)將使用 4 個(gè)字節(jié)。

位域聲明

    struct
    {
      type [member_name] : width ;
    };

下面是有關(guān)位域中變量元素的描述:

元素 描述
type 整數(shù)類(lèi)型亥揖,決定了如何解釋位域的值珊擂。類(lèi)型可以是整型、有符號(hào)整型、無(wú)符號(hào)整型摧扇。
member_name 位域的名稱(chēng)圣贸。
width 位域中位的數(shù)量。寬度必須小于或等于指定類(lèi)型的位寬度扳剿。

帶有預(yù)定義寬度的變量被稱(chēng)為位域旁趟。位域可以存儲(chǔ)多于 1 位的數(shù)昼激,例如庇绽,需要一個(gè)變量來(lái)存儲(chǔ)從 0 到 7 的值,您可以定義一個(gè)寬度為 3 位的位域橙困,如下:

 struct
    {
      unsigned int age : 3;
    } Age;

上面的結(jié)構(gòu)定義指示 C 編譯器瞧掺,age 變量將只使用 3 位來(lái)存儲(chǔ)這個(gè)值,如果您試圖使用超過(guò) 3 位凡傅,則無(wú)法完成辟狈。


二、isa探索

探索alloc 的時(shí)候夏跷,最后有個(gè)方法 initInstanceIsa 初始化 isa 并關(guān)聯(lián)類(lèi)哼转。在此繼續(xù)深入

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) {
        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;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

查看下 SUPPORT_INDEXED_ISA 的定義

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

__ARM_ARCH_7K__: 是代表手表的宏,不滿(mǎn)足
__arm64__: 表示64位ARM架構(gòu) 滿(mǎn)足
__LP64__: 表示指針長(zhǎng)度為64位 滿(mǎn)足
!__LP64__: 就不滿(mǎn)足了

所以 SUPPORT_INDEXED_ISA 為 0

由此分析上面的主要代碼為

        isa_t newisa(0);  // 聲明并初始化一個(gè)isa_t
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3; // 重點(diǎn)槽华,賦值了cls
        isa = newisa; // 賦值isa
2.1 isa_t結(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
};

通過(guò)源碼可知壹蔓,isa_t 是一個(gè)聯(lián)合體,里面有3個(gè)成員 cls 猫态,bits 佣蓉,struct,它們占用同一片內(nèi)存區(qū)域亲雪。

然后找到 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)

運(yùn)行環(huán)境是在mac上運(yùn)行的勇凭,不是跑的真機(jī),可以以 __x86_64__ 為例探索一下义辕。

綜上所述虾标,所以isa_t的聯(lián)合體,其中 shiftcls

  • arm64 下占33位(從第3位到35位)
  • __x86_64__ 下占44位(從第3位到46位)
union-isa_t存儲(chǔ).png
成員 介紹
nonpointer 表示是否對(duì) isa 指針開(kāi)啟指針優(yōu)化——0:純 isa 指針灌砖;1:不止是類(lèi)對(duì)象地址璧函,isa 中包含了類(lèi)信息、對(duì)象的引用計(jì)數(shù)等
has_assoc 關(guān)聯(lián)對(duì)象標(biāo)志位周崭,0沒(méi)有柳譬,1存在
has_cxx_dtor 該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯续镇, 如果沒(méi)有美澳,則可以更快的釋放對(duì)象
shiftcls 存儲(chǔ)類(lèi)指針的值,在開(kāi)啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類(lèi)指針
magic 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間
weakly_referenced 對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量制跟, 沒(méi)有弱引用的對(duì)象可以更快釋放
deallocating 標(biāo)志對(duì)象是否正在釋放內(nèi)存
has_sidetable_rc 當(dāng)對(duì)象引用技術(shù)大于 10 時(shí)舅桩,則需要借用該變量存儲(chǔ)進(jìn)位
extra_rc 當(dāng)表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1雨膨, 例如擂涛,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc 為 9聊记。如果引用計(jì)數(shù)大于 10撒妈, 則需要使用到下面的 has_sidetable_rc
2.2 shiftcls賦值 & 為什么右移3位(重點(diǎn))
newisa打印.png

打個(gè)斷點(diǎn),在將要賦值 shiftcls 的時(shí)候排监,現(xiàn)在里面的所有值狰右,都是因?yàn)?newisa.bits = ISA_MAGIC_VALUE 而來(lái)。(原因參考前置知識(shí)聯(lián)合體)

好的舆床,重點(diǎn)來(lái)了棋蚌,為什么賦值 shiftcls 的時(shí)候要右移3位?

cls 是一個(gè) Class 對(duì)象挨队,注意是對(duì)象谷暮,點(diǎn)進(jìn)去看一下定義

typedef struct objc_class *Class;
---
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    // 省略 ...
}

再繼續(xù)看 cache_tclass_data_bits_t 發(fā)現(xiàn)都是結(jié)構(gòu)體盛垦,然后看里面的成員變量湿弦,大部分都是 uintptr_t 類(lèi)型的,查看定義

typedef unsigned long           uintptr_t;

根據(jù)內(nèi)存對(duì)齊原則情臭,可知 Class 肯定是 8 字節(jié)對(duì)齊的省撑,同樣的,cls的指向地址(也既開(kāi)始地址)肯定是 8 的倍數(shù)俯在, 轉(zhuǎn)換成二進(jìn)制后竟秫,低三位肯定是 000

繼續(xù)上圖的打吁卫帧:

(lldb) p (uintptr_t)cls
(uintptr_t) $1 = 4294980728
(lldb) p/t (uintptr_t)cls
(uintptr_t) $2 = 0b0000000000000000000000000000000100000000000000000011010001111000
(lldb) 

再聯(lián)想聯(lián)合體的說(shuō)明肥败,共用內(nèi)存,可見(jiàn)蘋(píng)果設(shè)計(jì)優(yōu)化節(jié)省內(nèi)存的良苦用心愕提。
賦值 shiftcls 的時(shí)候既沒(méi)有改變 cls 的值馒稍,也最大的優(yōu)化了內(nèi)存使用。

我先開(kāi)始到分析到這的時(shí)候浅侨,還是有疑問(wèn)纽谒,存的時(shí)候是這樣,但是取的時(shí)候呢如输?取的時(shí)候前三位不是 000 啊鼓黔,對(duì)央勒,取的時(shí)候前三位確實(shí)不是 000,包括后面的幾位澳化。但是蘋(píng)果在取的時(shí)候崔步,又做了操作,接著往下分析

那賦值了 shiftcls 是不是就證明了我們關(guān)聯(lián)了類(lèi)呢缎谷?通常通過(guò) x/4gx 打印實(shí)例的時(shí)候井濒,第一個(gè)輸出的 8 字節(jié)到底是不是 isa 呢?

2.3 object_getClass 驗(yàn)證isa是否存的是class
        LGPerson *p = [LGPerson alloc];
        // #import <malloc/malloc.h>
        // Returns the class of an object.
        id tp = object_getClass(p); 

可在 objcruntime 里面的 objc-class.mm 源碼中查到

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

objc_object

inline Class 
objc_object::getIsa() 
{    // oc對(duì)象可認(rèn)為isTaggedPointer為false
    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;
}

會(huì)調(diào)用 ISA()

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
  // 上面分析過(guò) SUPPORT_INDEXED_ISA = 0
#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
}

最終返回的是 (Class)(isa.bits & ISA_MASK)列林。
__x86_64__# define ISA_MASK 0x00007ffffffffff8ULL

知道計(jì)算規(guī)則后瑞你,咱們驗(yàn)證下

驗(yàn)證isa指向class.png
(lldb) x/4gx p
0x10075eab0: 0x001d800100003445 0x0000000000000000
0x10075eac0: 0x0000000000000000 0x0000000000000000
(lldb) p tp
(id) $1 = 0x0000000100003440
(lldb) p 0x001d800100003445 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 4294980672
(lldb) p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL)
(Class) $3 = LGPerson
(lldb) p/x 4294980672
(long) $4 = 0x0000000100003440

總結(jié)

  1. 先打印p的內(nèi)存,取前8字節(jié)席纽,也就是 isa捏悬。
  2. 打印 tp撞蚕,是p實(shí)例的Class對(duì)象润梯。
  3. isaISA_MASK 相與。得到值用 p/x 16進(jìn)制打印甥厦,驗(yàn)證和 tp 一樣纺铭。
  4. p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL) 強(qiáng)轉(zhuǎn)類(lèi)型輸出也是LGPerson類(lèi)。
  5. 對(duì)應(yīng) newisa.shiftcls 賦值時(shí)右移三位刀疙,0x00007ffffffffff8 (__x86_64__) 轉(zhuǎn)成2進(jìn)制舶赔,可發(fā)現(xiàn) 0~247~63 位都是0谦秧,中間 3~46 的44位為1竟纳,所以取的就是 shiftcls
  6. 可驗(yàn)證上面所述疚鲤。

參考

C語(yǔ)言中文版:https://wiki.jikexueyuan.com/project/c/c-bit-fields.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锥累,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子集歇,更是在濱河造成了極大的恐慌桶略,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诲宇,死亡現(xiàn)場(chǎng)離奇詭異际歼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)姑蓝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)鹅心,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人纺荧,你說(shuō)我怎么就攤上這事旭愧∷萜” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵榕茧,是天一觀的道長(zhǎng)垃沦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)用押,這世上最難降的妖魔是什么肢簿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蜻拨,結(jié)果婚禮上池充,老公的妹妹穿的比我還像新娘。我一直安慰自己缎讼,他們只是感情好收夸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著血崭,像睡著了一般卧惜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夹纫,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天咽瓷,我揣著相機(jī)與錄音,去河邊找鬼舰讹。 笑死茅姜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的月匣。 我是一名探鬼主播钻洒,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锄开!你這毒婦竟也來(lái)了素标?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤院刁,失蹤者是張志新(化名)和其女友劉穎糯钙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體退腥,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡任岸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狡刘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片享潜。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嗅蔬,靈堂內(nèi)的尸體忽然破棺而出剑按,到底是詐尸還是另有隱情疾就,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布艺蝴,位于F島的核電站猬腰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猜敢。R本人自食惡果不足惜姑荷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缩擂。 院中可真熱鬧鼠冕,春花似錦、人聲如沸胯盯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)博脑。三九已至憎乙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趋厉,已是汗流浹背寨闹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留君账,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓沈善,卻偏偏與公主長(zhǎng)得像乡数,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闻牡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355