Non Fragile ivars

ivar結(jié)構(gòu)體

runtime的源碼中,可以看到類結(jié)構(gòu)體中有成員變量的列表.(class_ro_t也是屬于類結(jié)構(gòu)體中的一個(gè)成員,不過(guò)需要通過(guò)non-pointer isas來(lái)訪問(wèn)).

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    // ....
    const ivar_list_t * ivars;
    // ....
};

每個(gè)成員變量又是一個(gè)結(jié)構(gòu)體.

struct ivar_list_t {
    uint32_t entsize;//總大小
    uint32_t count;//個(gè)數(shù)
    ivar_t first;//第一個(gè)變量的結(jié)構(gòu)體
};

ivar_t結(jié)構(gòu)體的布局如下.

struct ivar_t {
    int32_t *offset;//在實(shí)例對(duì)象中的偏移
    const char *name;//變量名
    const char *type;//變量類型
    //....
}

假設(shè)MyObject類(繼承自NSObject)有兩個(gè)數(shù)組屬性.那么類的成員屬性列表(ivar_list_t)看起來(lái)很可能是這樣的:

子類并不是通過(guò)基類ivar_list_t中的entsize總大小得到子類自己的成員變量在實(shí)例對(duì)象中的偏移,而是在編譯器,這點(diǎn)后面詳談.

二進(jìn)制兼容性

Objective-C的runtime分為兩個(gè)版本.一個(gè)是legacy版本,一個(gè)是modern版本.modern版本是新的runtime版本,它跟隨著Objective-C 2.0一起推出的,增加了許多新的特性.

最重要的特性是成員變量(ivar)在modern runtime中是non-fragile.

  • In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.
  • In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.

怎么理解這兩句話呢?

在舊版本的runtime中,MyObject類的布局如圖,MyObject的成員變量排在基類NSObject的成員后面,這點(diǎn)上面也提到過(guò).

如果蘋果更新了SDK版本,假設(shè)NSObject增加了兩個(gè)成員變量.原來(lái)寫的程序?qū)o(wú)法正常運(yùn)作,因?yàn)?code>MyObject類成員變量布局在編譯期已經(jīng)確定了,這時(shí)兩個(gè)成員變量和基類的內(nèi)存區(qū)域重疊了.在重新編譯代碼之前,程序無(wú)法在新版本的系統(tǒng)上運(yùn)行.列舉一個(gè)更通俗一點(diǎn)的情況,當(dāng)我們?cè)陟o態(tài)庫(kù)中使用了繼承自NSObject的類,如果這個(gè)第三方靜態(tài)庫(kù)沒(méi)有重新編譯的話,程序可能就廢了...這時(shí)只能等待作者更新,或者更換一個(gè)第三方庫(kù).

這時(shí)modern runtime帶著新特性Non Fragile ivars登場(chǎng)了.

runtime在加載MyObject類的時(shí)候(注:runtime加載類是在main函數(shù)跑起來(lái)之前),會(huì)計(jì)算基類的大小.runtime在運(yùn)行期判斷子類的instanceStart大小和父類instanceSize大小(關(guān)于這兩個(gè)成員請(qǐng)看文章開(kāi)頭展示的結(jié)構(gòu)體內(nèi)容),如果子類的instanceStart小于父類的instanceSize,說(shuō)明父類新增了成員變量,子類的成員變量需要進(jìn)行偏移.

在上圖的例子中,當(dāng)MyObjectinstanceStart小于NSObjectinstanceSize,MyObject在編譯器確定的結(jié)構(gòu)體將會(huì)動(dòng)態(tài)調(diào)整成員變量偏移,因此程序無(wú)需重新編譯,就能在新版本的系統(tǒng)上運(yùn)行.

因此,這個(gè)特性讓OC的庫(kù)具有了二進(jìn)制兼容性,即穩(wěn)固的ABI.

runtime實(shí)現(xiàn)

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    // ....
    const uint8_t * ivarLayout;
    const ivar_list_t * ivars;
    // ....
};

該結(jié)構(gòu)體中的instanceStart,和instanceSize在編譯器都會(huì)被編譯器賦值,成為runtime運(yùn)行期動(dòng)態(tài)調(diào)整成員變量的判斷依據(jù).

static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro_t*& ro) 
{
  // ....
  // 當(dāng)子類的instanceStart小于父類的instanceSize時(shí),說(shuō)明需要調(diào)整
    if (ro->instanceStart < super_ro->instanceSize) {
        // Superclass has changed size. This class's ivars must move.
        // Also slide layout bits in parallel.
        // This code is incapable of compacting the subclass to 
        //   compensate for a superclass that shrunk, so don't do that.
        // ....
        // 讓只讀區(qū)域可寫
        class_ro_t *ro_w = make_ro_writeable(rw);
        ro = rw->ro;
        // 調(diào)整成員變量
        moveIvars(ro_w, super_ro->instanceSize, 
                  mergeLayouts ? &ivarBitmap : nil, 
                  mergeLayouts ? &weakBitmap : nil);
        // layoutsChanged標(biāo)識(shí)布局改變了,ivarLayout需要改變
        layoutsChanged = YES;
    } 
  // ....
}

重點(diǎn)看看moveIvars的實(shí)現(xiàn),簡(jiǎn)化如下:

static void moveIvars(class_ro_t *ro, uint32_t superSize, 
                      layout_bitmap *ivarBitmap, layout_bitmap *weakBitmap)
{
    // 紀(jì)錄偏移
    uint32_t diff;
    // 偏移是父類的instanceSize減去子類的instanceStart
    diff = superSize - ro->instanceStart;
    // ....
  
    // Slide all of this class's ivars en masse
    // 遍歷子類的所有成員變量
    for (i = 0; i < ro->ivars->count; i++) {
        // 拿到第i個(gè)成員變量
        ivar_t *ivar = ivar_list_nth(ro->ivars, i);
        // 得到原來(lái)記錄的偏移量
        uint32_t oldOffset = (uint32_t)*ivar->offset;
        // 在原來(lái)的基礎(chǔ)上加上額外的偏移量
        uint32_t newOffset = oldOffset + diff;
        *ivar->offset = newOffset;
    }
    // 最后,別忘了instanceStart和instanceSize也要加偏移
     *(uint32_t *)&ro->instanceStart += diff;
     *(uint32_t *)&ro->instanceSize += diff; 
}

我們注意到ivar_t中的offset是個(gè)int *指針,而不是一個(gè)int類型的變量,之所以要這樣設(shè)計(jì),就是為了不讓偏移在編譯器固定死,讓runtime在運(yùn)行期也能動(dòng)態(tài)的修改偏移量.

不能動(dòng)態(tài)添加成員變量的原因

我們知道,在設(shè)計(jì)分類的時(shí)候,是不能夠在分類中添加成員變量的,那么這是為什么呢?
從上述的角度來(lái)看,這是因?yàn)?

分類是在主類之后被加載到runtime的,這時(shí)候類結(jié)構(gòu)已經(jīng)確定下來(lái)了.如果這時(shí)進(jìn)行成員變量的添加,那么當(dāng)子類加載的時(shí)候,就會(huì)出現(xiàn)文章之前描述的內(nèi)存覆蓋的現(xiàn)象.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,409評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,569評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,360評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,895評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,123評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,559評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,742評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,981評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,363評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,622評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,354評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,707評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過(guò)整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 936評(píng)論 0 6
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,578評(píng)論 33 466
  • 作者:梁實(shí)秋 鐘表上的時(shí)針是在慢慢的移動(dòng)著的,移動(dòng)的如此之慢,使你幾乎不感覺(jué)到它的移動(dòng),人的年紀(jì)也是這樣的,一年又...
    閆宇sofia閱讀 1,553評(píng)論 0 1
  • 2、ORACEL 數(shù)據(jù)類型 對(duì)應(yīng)NUMBER類型的事例: NUMBER類型 對(duì)于日起類型,可以使用sysdate內(nèi)...
    閑不住的李先森閱讀 233評(píng)論 0 0
  • 不論換多少個(gè)音樂(lè)播放器,更新多少次歌單,古風(fēng)總是在我喜愛(ài)的音樂(lè)中占據(jù)一席之地。入夏以來(lái),天氣燥熱,就更是基本只聽(tīng)古...
    宋小溫閱讀 1,999評(píng)論 51 68