OC底層-ISA的前生今世

在前面的OC底層-對(duì)象的alloc流程探究文章中管搪,alloc
的流程中,我們知道了OC底層是通過(guò)initInstanceIsa把我們的類clsisa關(guān)聯(lián)起來(lái)馆铁,我們順著initInstanceIsa去對(duì)今天的主角isa一探究竟蚯瞧。

isa是什么

isa的類型

struct objc_object 
private:
    isa_t isa;

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

可以看到在objc_object結(jié)構(gòu)體中,isaisa_t類型颁虐。同時(shí)我們找到了isa_t的定義。union是聯(lián)合體卧须,這里我們的isa顯然就是聯(lián)合體類型另绩。

unionstruct

union

聯(lián)合體(也稱共用體)是由不同的數(shù)據(jù)類型組成儒陨,但其成員是互斥的,所有的成員共占一段內(nèi)存笋籽。聯(lián)合體采用了內(nèi)存覆蓋技術(shù)蹦漠,同一時(shí)刻只能保存一個(gè)成員的值,如果對(duì)新的成員賦值车海,就會(huì)將原來(lái)成員的值覆蓋掉笛园。

  • 優(yōu)點(diǎn):所有成員共用一段內(nèi)存,節(jié)省了內(nèi)存空間侍芝。
  • 缺點(diǎn):包容性弱

struct

結(jié)構(gòu)體是指把不同的數(shù)據(jù)組合成一個(gè)整體研铆,其成員是共存的,成員不管是否使用州叠,都會(huì)分配內(nèi)存棵红。

  • 優(yōu)點(diǎn):存儲(chǔ)容量較大,包容性強(qiáng)咧栗,且成員之間不會(huì)相互影響窄赋。
  • 缺點(diǎn):所有成員不管是否使用都分配內(nèi)存,比較浪費(fèi)內(nèi)存楼熄。

兩者的區(qū)別

  • 內(nèi)存占用情況
    • 聯(lián)合體的所有成員占用同一段內(nèi)存,成員互斥
    • 結(jié)構(gòu)體的各個(gè)成員會(huì)占用不同的內(nèi)存浩峡,互相之間沒(méi)有影響
  • 內(nèi)存分配大小
    • 聯(lián)合體占用的內(nèi)存等于最大的成員占用的內(nèi)存
    • 結(jié)構(gòu)體內(nèi)存等于最大的成員占用的內(nèi)存的整數(shù)倍

isa_t 的成員分析

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


# 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)

# else
#   error unknown architecture for packed isa
# endif

通過(guò)聯(lián)合體結(jié)構(gòu)體的特性可岂,不難分析出isa_t采用聯(lián)合體是基于內(nèi)存優(yōu)化的考慮。isa指針的內(nèi)存大小是8字節(jié)翰灾,即64bit缕粹,也就是64位。isa通過(guò)char+位域的設(shè)計(jì)纸淮,巧妙的用二進(jìn)制的每一位來(lái)存儲(chǔ)和表示聯(lián)合體成員的信息平斩。

  • isa_t提供了的對(duì)應(yīng)的初始化方法isa_t() { }isa_t(uintptr_t value) : bits(value) { }

  • isa_t還提供了2個(gè)成員咽块。clsbits绘面,以及一個(gè)結(jié)構(gòu)體定義的位域,用來(lái)存儲(chǔ)類的信息。

  • 分析下位域中存儲(chǔ)的每個(gè)數(shù)據(jù)

    • nonpointer :表示是否對(duì) isa 指針開啟指針優(yōu)化侈沪。0:純isa指針揭璃,1:不止是類對(duì)象地址,isa 中包含了類信息亭罪、對(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ǔ)類指針的值肆氓。開啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類指針亚脆。
    • magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間做院。
    • weakly_referenced:標(biāo)志對(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

下面是x86_64macOS平臺(tái)的isa_t的成員存儲(chǔ)情況
[圖片上傳失敗...(image-519ad0-1600051522675)]

isa做了什么

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

isa成員賦值

  • alloc一樣奶赔,我們?cè)?code>main.m中初始化MuPerson惋嚎。由alloc-> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone > _class_createInstanceFromZone > initInstanceIsa > initIsa 到上面這份核心代碼,打上斷點(diǎn)站刑,開始調(diào)試另伍,我們來(lái)看系統(tǒng)是如何對(duì)isa的成員賦值。
  • 初始化isa指針绞旅,這里依據(jù)nonpointer來(lái)分別通過(guò)clsbits對(duì)isa初始化摆尝,也從側(cè)面驗(yàn)證了clsbits互斥的原則,不能同時(shí)setter因悲。
  • 斷點(diǎn)過(guò)isa_t newisa(0)堕汞,對(duì)isa初始化,到bits賦值之前晃琳,我們通過(guò)p打印newisa結(jié)果如下
    image
  • 斷點(diǎn)過(guò)newisa.bits = ISA_MAGIC_VALUE讯检,對(duì)bits賦值之后,我們通過(guò)p打印newisa結(jié)果如下
    image
  • 斷點(diǎn)到isa = newisa卫旱,對(duì)isa賦值之后视哑,我們通過(guò)p打印newisa結(jié)果如下
    image
  • 三次斷點(diǎn)p打印的newisa結(jié)果做下對(duì)比如下
    image

通過(guò)斷點(diǎn),我們清晰的看到isa中的成員是如何一步一步的被賦值誊涯。

isa關(guān)聯(lián)

image

通過(guò)對(duì)isa成員賦值分析挡毅,isa和我們的關(guān)聯(lián)關(guān)鍵在于isa中的成員shiftcls,即newisa.shiftcls = (uintptr_t)cls >> 3這句關(guān)鍵代碼。這里>> 3是為了通過(guò)位運(yùn)算把cls的信息準(zhǔn)確的放在shiftcls的存儲(chǔ)位置上暴构。以這樣一種巧妙的方式跪呈,就把isa關(guān)聯(lián)起來(lái)段磨。

isa是否和類關(guān)聯(lián)的驗(yàn)證

  • 通過(guò) isa & ISA_MSAK驗(yàn)證
(lldb) po cls
MuPerson

(lldb) po obj
<MuPerson: 0x1007076b0>

(lldb) x/4gx obj
0x1007076b0: 0x001d800100002255 0x0000000000000000
0x1007076c0: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d800100002255 & 0x00007ffffffffff8ULL
MuPerson

(lldb) 
  • 通過(guò) object_getClass驗(yàn)證

object_getClass -> Class object_getClass -> inline Class objc_object::getIsa() -> inline Class objc_object::ISA()

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
}
  • 通過(guò) 位運(yùn)算 驗(yàn)證
(lldb) p cls
(Class) $7 = MuPerson
(lldb) p/x cls
(Class) $8 = 0x0000000100002250 MuPerson
(lldb) p obj
(MuPerson *) $9 = 0x000000010192c440
(lldb) x/4gx obj
0x10192c440: 0x001d800100002255 0x0000000000000000
0x10192c450: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100002255 >> 3
(long) $11 = 0x0003b0002000044a
(lldb) p/x $11 << 20
(long) $12 = 0x0002000044a00000
(lldb) p/x $12 >> 17
(long) $13 = 0x0000000100002250
(lldb) 

為什么設(shè)計(jì)isa

isa走位流程圖

這里放上業(yè)界經(jīng)典isa走位流程圖

image

  • 子類的isa走位鏈:子類對(duì)象 --> 子類 --> 子元類 --> NSObject(根元類) --> NSObject(根元類,即自己)
  • 父類的isa走位鏈:父類對(duì)象 --> 父類 --> 父元類 --> NSObject(根元類) --> NSObject(根元類耗绿,即自己)
  • 類的繼承關(guān)系鏈:子類 --> 父類 --> NSObject(根類)--> nil
  • 元類的繼承關(guān)系鏈:子元類 --> 父元類 --> 根元類 --> NSObject(根類)--> nil

objc_class & objc_object

struct NSObject_IMPL {
    Class isa;
};

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

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ///此處省略一萬(wàn)行
}


struct objc_object {
private:
    isa_t isa;
    ///此處省略一萬(wàn)行
}
  • clang編譯的mian.cpp文件中苹支,NSObject中的isa在底層是由Class 定義的,其中Class的底層編碼來(lái)自 objc_class類型误阻,而objc_class又繼承于objc_object债蜜。
  • OC層面的根類NSObject,初始化對(duì)象就會(huì)有isa特性,isa的根源來(lái)自OC底層的objc_object究反。
  • 所有的是由objc_class為模板生成的寻定,所有的對(duì)象則是由objc_object為模板生成的。

貫穿全局的isa

上面我們微觀的分析了isa做了什么精耐,isa對(duì)象關(guān)聯(lián)起來(lái)狼速。宏觀的看,isa貫穿了我們的類對(duì)象卦停、向胡、元類根元類惊完。在OC底層系統(tǒng)為我們?cè)O(shè)計(jì)了objc_object僵芹、objc_class,OC上層由NSObject作為根類持有isa特性。isa完美的將底層OC串聯(lián)起來(lái)小槐。

結(jié)語(yǔ):萬(wàn)物皆對(duì)象淮捆,萬(wàn)物皆有isa

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末本股,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子桐腌,更是在濱河造成了極大的恐慌拄显,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件案站,死亡現(xiàn)場(chǎng)離奇詭異躬审,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蟆盐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門承边,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人石挂,你說(shuō)我怎么就攤上這事博助。” “怎么了痹愚?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵富岳,是天一觀的道長(zhǎng)蛔糯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)窖式,這世上最難降的妖魔是什么蚁飒? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮萝喘,結(jié)果婚禮上淮逻,老公的妹妹穿的比我還像新娘。我一直安慰自己阁簸,他們只是感情好爬早,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著强窖,像睡著了一般凸椿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上翅溺,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天脑漫,我揣著相機(jī)與錄音,去河邊找鬼咙崎。 笑死优幸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的褪猛。 我是一名探鬼主播网杆,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伊滋!你這毒婦竟也來(lái)了碳却?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笑旺,失蹤者是張志新(化名)和其女友劉穎昼浦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筒主,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡关噪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乌妙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片使兔。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藤韵,靈堂內(nèi)的尸體忽然破棺而出虐沥,到底是詐尸還是另有隱情,我是刑警寧澤泽艘,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布置蜀,位于F島的核電站奈搜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盯荤。R本人自食惡果不足惜馋吗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秋秤。 院中可真熱鬧宏粤,春花似錦、人聲如沸灼卢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鞋真。三九已至崇堰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涩咖,已是汗流浹背海诲。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留檩互,地道東北人特幔。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像闸昨,于是被迫代替她去往敵國(guó)和親蚯斯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355