03 - isa結構探索及關聯(lián)類

今天的主題是探索isa的結構 在此之前我們需要先了解下什么是聯(lián)合體

構造數(shù)據(jù)類型的方式有以下兩種 :

  • 結構體 (struct)
  • 聯(lián)合體 (union ,也稱為共用體)

結構體

結構體是指把不同的數(shù)據(jù)組合成一個整體,其變量是共存的咖杂,變量不管是否使用滤馍,都會分配內存狂打。

  • 缺點:所有屬性都分配內存,比較浪費內存僚碎,假設有4個int成員相满,一共分配了16字節(jié)的內存,但是在使用時,你只使用了4字節(jié)诗箍,剩余的12字節(jié)就是屬于內存的浪費

  • 優(yōu)點:存儲容量較大癣籽,包容性強,且成員之間不會相互影響

聯(lián)合體

聯(lián)合體也是由不同的數(shù)據(jù)類型組成滤祖,但其變量是互斥的筷狼,所有的成員共占一段內存。而且共用體采用了內存覆蓋技術匠童,同一時刻只能保存一個成員的值埂材,如果對新的成員賦值,就會將原來成員的值覆蓋

  • 缺點:汤求,包容性弱

  • 優(yōu)點:所有成員共用一段內存俏险,使內存的使用更為精細靈活严拒,同時也節(jié)省了內存空間

兩者的區(qū)別

  • 內存占用情況

  • 結構體的各個成員會占用不同的內存,互相之間沒有影響

  • 共用體的所有成員占用同一段內存竖独,修改一個成員會影響其余所有成員

  • 內存分配大小

  • 結構體內存 >= 所有成員占用的內存總和(成員之間可能會有縫隙)

  • 共用體占用的內存等于最大的成員占用的內存

現(xiàn)在我們已經(jīng)大致了解什么是聯(lián)合體,下面進行isa探索,上代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        
        Person *objc = [Person alloc];

        NSLog(@"Hello, World!  %@",objc);
    }
    return 0;
}

alloc的源碼跟進去到關聯(lián)指針和類的步驟:

if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

跟進obj->initInstanceIsa(cls, hasCxxDtor);

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, 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); // isa 初始化
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);  // isa 初始化
#if SUPPORT_INDEXED_ISA // !nopointmenter 執(zhí)行的流程 ,即isa通過cls定義
        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 //bits 時執(zhí)行的流程
        newisa.bits = ISA_MAGIC_VALUE;//bist進行賦值  為0x001d800000000001ULL
        // 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條件是否滿足裤唠,都會生成一個isa_t的類型。跟進去可以發(fā)現(xiàn):

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_t類型使用聯(lián)合體的原因也是基于內存優(yōu)化的考慮莹痢,這里的內存優(yōu)化是指在isa指針中通過char + 位域(即二進制中每一位均可表示不同的信息)的原理實現(xiàn)种蘸。通常來說,isa指針占用的內存大小是8字節(jié)竞膳,即64位航瞭,已經(jīng)足夠存儲很多的信息了,這樣可以極大的節(jié)省內存坦辟,以提高性能

isa_t的定義中可以看出:

提供了兩個成員刊侯,clsbits,由聯(lián)合體的定義所知长窄,這兩個成員是互斥的滔吠,也就意味著,當初始化isa指針時挠日,有兩種初始化方式

通過cls初始化疮绷,bits無默認值

通過bits初始化,cls有默認值

還提供了一個結構體定義的位域嚣潜,用于存儲類信息及其他信息冬骚,結構體的成員ISA_BITFIELD,這是一個宏定義懂算,有兩個版本 __arm64__(對應ios 移動端) 和__x86_64__(對應macOS)只冻,以下是它們的一些宏定義,如下圖所

位域宏定義

其中存儲的成員信息:

  • nonpointer一般自定義的類都是這個類型的计技,而系統(tǒng)類才會有純isa指針的情況喜德,占1位。
    0:純isa指針垮媒。
    1:不只是類對象地址舍悯,isa中包含了類信息、對象的引用計數(shù)等睡雇。
  • has_assoc關聯(lián)對象標志位萌衬,0代表沒有關聯(lián)對象,1代表存在關聯(lián)對象它抱,占1位秕豫。
  • has_cxx_dtor該對象是否有C++或OC的析構器,如果有析構函數(shù)观蓄,則需要做析構邏輯混移,如果沒有祠墅,則可以更快釋放對象,占1位沫屡。
  • shiftclx存儲類指針的值饵隙, 也就是類信息,開啟指針優(yōu)化的情況下沮脖,在arm64架構中有33位用來存儲類指針金矛,`x86_64``架構中占44位。
  • magic 用于調試器判斷當前對象是真的對象還是沒有初始化的空間勺届,占6位驶俊。
  • weakly_refrenced是指對象是否被指向或者曾經(jīng)指向一個ARC的弱變量,沒有弱引用的對象可以更快釋放免姿。
  • deallocating 標志對象是否正在釋放內存饼酿。
  • has_sidetable_rc當對象引用計數(shù)大于2的19次方(x86_64架構為2的8次方)時,則需要存儲到散列表胚膊,這時該變量值變?yōu)?code>true故俐。
  • extra_rc表示該對象的``引用計數(shù)值,最大為2的19次方(x86_64架構為2的8次方)紊婉,實際上是引用計數(shù)值減1药版,,如果大于最大容量喻犁,就需要取一半計數(shù)存到散列表中槽片,真機上最多有8張散列表存儲對象引用計數(shù),x86_64則最多64張肢础,這時上面的has_sidetable_rc值置為true还栓。

針對兩種不同平臺,其isa的存儲情況如圖所示

bits位域分布

下面驗證alloc過程中isa是如何跟進行關聯(lián)的

上面 initIsa方法的源碼實現(xiàn)我們可以發(fā)現(xiàn)邏輯主要分為兩部分

  • 通過 cls 初始化 isa
  • 通過 bits 初始化 isa

運行源碼 斷點如圖 打印newisa信息

調試結果

-繼續(xù)執(zhí)行传轰,到newisa.bits = ISA_MAGIC_VALUE;下一行剩盒,表示為isa的bits成員賦值,重新執(zhí)行l(wèi)ldb命令p newisa慨蛙,得到的結果如下

調試結果

對比發(fā)現(xiàn)數(shù)據(jù)發(fā)生變化,可以看到賦值后的nonpointer已經(jīng)是1了辽聊,magic為59。

  • 其中magic59是由于將isa指針地址轉換為二進制股淡,從47(因為前面有4個位域,共占用47位廷区,地址是從0開始)位開始讀取6位唯灵,再轉換為十進制,如下圖所示
計算器結果

此時此刻隙轻,可以得出結果埠帕,這magic的59確實是由0x001d800000000001ULL填進去的垢揩。

繼續(xù)執(zhí)行 過newisa.shiftcls = (uintptr_t)cls >> 3;這一步 打印newisa

賦值結果
  • shiftcls0變成了536871965即為 (uintptr_t)cls >> 3
為什么在shiftcls賦值時需要類型強轉

因為內存的存儲不能存儲字符串敛瓷,機器碼只能識別0 叁巨、1這兩種數(shù)字,所以需要將其轉換為uintptr_t數(shù)據(jù)類型呐籽,這樣shiftcls中存儲的類信息才能被機器碼理解锋勺, 其中uintptr_t是long

為什么需要右移3位

主要是由于shiftcls處于isa指針地址的中間部分狡蝶,前面還有3個位域庶橱,為了不影響前面的3位域的數(shù)據(jù),需要右移將其抹零

進一步驗證類與指針對象相關聯(lián)

位運算流程圖
    1. 控制臺打印這個obj指針贪惹,并將isa的內存地址右移3位苏章,再左移20位,再右移17位奏瞬,這時再打印地址移動之后的isa的地址枫绅,可以看到,就是我們上面關聯(lián)的類硼端,也就是說這時對象指針和類關聯(lián)上了, 如圖
    1. 通過isa & ISA_MSAK 如圖
位運算驗證結果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末并淋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子显蝌,更是在濱河造成了極大的恐慌预伺,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曼尊,死亡現(xiàn)場離奇詭異酬诀,居然都是意外死亡,警方通過查閱死者的電腦和手機骆撇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門瞒御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人神郊,你說我怎么就攤上這事肴裙。” “怎么了涌乳?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵蜻懦,是天一觀的道長。 經(jīng)常有香客問我夕晓,道長宛乃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮征炼,結果婚禮上析既,老公的妹妹穿的比我還像新娘。我一直安慰自己谆奥,他們只是感情好眼坏,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酸些,像睡著了一般宰译。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上擂仍,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天囤屹,我揣著相機與錄音,去河邊找鬼逢渔。 笑死肋坚,一個胖子當著我的面吹牛,可吹牛的內容都是我干的肃廓。 我是一名探鬼主播智厌,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盲赊!你這毒婦竟也來了铣鹏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤哀蘑,失蹤者是張志新(化名)和其女友劉穎诚卸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绘迁,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡合溺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缀台。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棠赛。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膛腐,靈堂內的尸體忽然破棺而出睛约,到底是詐尸還是另有隱情,我是刑警寧澤哲身,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布辩涝,位于F島的核電站,受9級特大地震影響勘天,放射性物質發(fā)生泄漏怔揩。R本人自食惡果不足惜棍丐,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沧踏。 院中可真熱鬧,春花似錦巾钉、人聲如沸翘狱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潦匈。三九已至,卻和暖如春赚导,著一層夾襖步出監(jiān)牢的瞬間茬缩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工吼旧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凰锡,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓圈暗,卻偏偏與公主長得像掂为,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子员串,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361