OC底層原理--isa結(jié)構(gòu)分析

isa對于大家來說應(yīng)該并不陌生,不管是各個公司的面試題或者說是平時的開發(fā)當(dāng)中都會經(jīng)常被提及,另外在我們之前對alloc的源碼分析時也發(fā)現(xiàn),最后一步obj->initInstanceIsa(cls, hasCxxDtor)便是對isa的初始化.今天我們就跟隨源碼一起來看一看isa到底是個怎樣的存在

聯(lián)合體位域

在講isa之前,我們先來學(xué)習(xí)一個概念:聯(lián)合體位域.

聯(lián)合體與結(jié)構(gòu)體

結(jié)構(gòu)體(struct)中所有變量是"共存"的--優(yōu)點是"有容乃大",全面;缺點是內(nèi)存空間的分配是粗放的,不管用不用,全分配

聯(lián)合體(union)中各變量是"互斥"的--缺點是不夠"包容";優(yōu)點是內(nèi)存使用更為精細(xì)靈活,節(jié)省了內(nèi)存空間

舉例說明:
存在如下四個屬性

@property (nonatomic, assign) int front; // 1:正在向前 2:沒有向前
@property (nonatomic, assign) int back;  // 1:正在向后 2:沒有向后
@property (nonatomic, assign) int left;  // 1:正在向左 2:沒有向左
@property (nonatomic, assign) int right; // 1:正在向右 2:沒有向右

如上,每個int占用四個字節(jié),總共占用4*4=16個字節(jié),共16*8=128位,造成了大部分內(nèi)存浪費,
而用聯(lián)合體位域表現(xiàn)如下:

// 聯(lián)合體
union {
    char bits; // 占用1位,8個字節(jié),二進(jìn)制表示為0b00000000
    // 位域
    struct { // 0000 1111
        char front  : 1; // 占用bits的第一個字節(jié)
        char back   : 1; // 占用bits的第二個字節(jié)
        char left   : 1; // 占用bits的第三個字節(jié)
        char right  : 1; // 占用bits的第四個字節(jié)
    };
} _direction;

#define LYDirectionFrontMask    (1 << 0)
#define LYDirectionBackMask     (1 << 1)
#define LYDirectionLeftMask     (1 << 2)
#define LYDirectionRightMask    (1 << 3)n

// 初始化
_direction.bits = 0b0000000000;

// 賦值
_direction.bits |= LYDirectionFrontMask;

// 取值
return _direction.front;

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

ISA_BITFIELD定義如下:
// arm64架構(gòu)下
#   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

// x86_64架構(gòu)
#   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

通過源碼我們可以發(fā)現(xiàn)isa采用了聯(lián)合體的結(jié)構(gòu).arm64架構(gòu)與x86_64架構(gòu)主要區(qū)別在shiftclsextra_rc占用的長度.

isa存儲內(nèi)容

接下來我們一起看下isa中定義的各個字段具體指什么(arm64架構(gòu)下)

  • nonpointer(存儲在第0字節(jié))是否為優(yōu)化isa標(biāo)志。0代表是優(yōu)化前的isa,一個純指向類或元類的指針;1表示優(yōu)化后的isa立哑,不止是一個指針挑庶,isa中包含類信息来候、對象的引用計數(shù)等⌒独現(xiàn)在基本上都是優(yōu)化后的isa好乐。

  • has_assoc(存儲在第1個字節(jié))關(guān)聯(lián)對象標(biāo)志位蛔溃。對象含有或者曾經(jīng)含有關(guān)聯(lián)引用绰沥,0表示沒有,1表示有城榛,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存(dealloc的底層代碼有體現(xiàn))揪利。

  • has_cxx_dtor(存儲在第2個字節(jié))析構(gòu)函數(shù)標(biāo)志位,如果有析構(gòu)函數(shù)狠持,則需進(jìn)行析構(gòu)邏輯疟位,如果沒有,則可以更快速地釋放對象(dealloc的底層代碼有體現(xiàn))喘垂。

  • shiftcls(存儲在第3-35字節(jié))存儲類的指針甜刻,其實就是優(yōu)化之前isa指向的內(nèi)容绍撞。在arm64架構(gòu)中有33位用來存儲類指針。x86_64架構(gòu)有44位得院。

  • magic(存儲在第36-41字節(jié))判斷對象是否初始化完成傻铣, 是調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間。

  • weakly_referenced(存儲在第42字節(jié))對象被指向或者曾經(jīng)指向一個ARC的弱變量祥绞,沒有弱引用的對象可以更快釋放(dealloc的底層代碼有體現(xiàn))非洲。

  • deallocating(存儲在第43字節(jié))標(biāo)志對象是否正在釋放內(nèi)存。

  • has_sidetable_rc(存儲在第44字節(jié))判斷該對象的引用計數(shù)是否過大蜕径,如果過大則需要其他散列表來進(jìn)行存儲两踏。

  • extra_rc(存儲在第45-63字節(jié)。)存放該對象的引用計數(shù)值減1后的結(jié)果兜喻。對象的引用計數(shù)超過 1梦染,會存在這個里面,如果引用計數(shù)為 10,extra_rc的值就為 9

由上我們可以知道shiftcls中存儲了類的相關(guān)信息,接下來我們通過源碼來驗證下

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

通過代碼我們可以發(fā)現(xiàn)所謂的isa和對象進(jìn)行關(guān)聯(lián)就是將cls的信息存儲在新的isashiftcls上,因為類信息的存儲是從第四位開始的,所以需要將cls右移3位,即:newisa.shiftcls = (uintptr_t)cls >> 3;

我們再來看下newisa賦值前后的值分別是什么

// 賦值前,即執(zhí)行newisa.shiftcls = (uintptr_t)cls >> 3;前
(lldb) p newisa
(isa_t) $4 = {
  cls = 0x001d800000000001
  bits = 8303511812964353
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}

// 賦值后
(lldb) p newisa
(isa_t) $5 = {
  cls = LYPerson
  bits = 8303516107940081
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 536871966
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
} 

通過賦值前后的對比我們發(fā)現(xiàn)新的isacls變?yōu)榱?code>LYPerson,shiftcls也由0變成了536871966.由此也證明了isa與對象關(guān)聯(lián)就是將類信息存入到isa的shiftcls

除此之外,我們還可以通過移位來證明,因為arm64shiftcls占用3~35共33位,所以我們可以通過先右移3位,再左移30位,最后再右移27位來獲取shiftcls的值,如下:

(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d8001000024dd >> 3
1037939513492635

(lldb) po 1037939513492635 << 30
562951189692416

(lldb) po 562951189692416 >> 27
Person

(lldb)

當(dāng)然,對于左移右移我們還可以通過位運(yùn)算&來實現(xiàn),如下

#   define ISA_MASK        0x0000000ffffffff8ULL
(lldb) po 0x001d8001000024dd & 0x0000000ffffffff8ULL
Person

至此,我們對isa的底層源碼以及存儲的值信息已經(jīng)有了一定的了解,以后遇到關(guān)于isa的相關(guān)問題也能回答的更自信一些了.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朴皆,一起剝皮案震驚了整個濱河市帕识,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遂铡,老刑警劉巖肮疗,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忧便,居然都是意外死亡族吻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門珠增,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砍艾,你說我怎么就攤上這事蒂教。” “怎么了脆荷?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵凝垛,是天一觀的道長。 經(jīng)常有香客問我蜓谋,道長梦皮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任桃焕,我火速辦了婚禮剑肯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘观堂。我一直安慰自己让网,他們只是感情好呀忧,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溃睹,像睡著了一般而账。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上因篇,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天泞辐,我揣著相機(jī)與錄音,去河邊找鬼竞滓。 笑死咐吼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虽界。 我是一名探鬼主播汽烦,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼莉御!你這毒婦竟也來了撇吞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤礁叔,失蹤者是張志新(化名)和其女友劉穎牍颈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琅关,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡煮岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涣易。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片画机。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖新症,靈堂內(nèi)的尸體忽然破棺而出步氏,到底是詐尸還是另有隱情,我是刑警寧澤徒爹,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布荚醒,位于F島的核電站,受9級特大地震影響隆嗅,放射性物質(zhì)發(fā)生泄漏界阁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一胖喳、第九天 我趴在偏房一處隱蔽的房頂上張望泡躯。 院中可真熱鬧,春花似錦、人聲如沸精续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽重付。三九已至顷级,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間确垫,已是汗流浹背弓颈。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留删掀,地道東北人翔冀。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像披泪,于是被迫代替她去往敵國和親纤子。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350