對(duì)象的內(nèi)存結(jié)構(gòu)與強(qiáng)壯的成員變量

如果把類的實(shí)例看成一個(gè)C語(yǔ)言的結(jié)構(gòu)體(struct)

首先包含的是一個(gè) isa 指針

類的其它成員變量依次排列在結(jié)構(gòu)體中


對(duì)象在內(nèi)存中的排布可以看成一個(gè)結(jié)構(gòu)體辛藻,該結(jié)構(gòu)體的大小并不能動(dòng)態(tài)變化。所以無法在運(yùn)行時(shí)動(dòng)態(tài)給對(duì)象增加成員變量谦屑。

需要特別說明一下吧慢,通過 objc_setAssociatedObject 和 objc_getAssociatedObject方法可以變相地給對(duì)象增加成員變量涛漂,但由于實(shí)現(xiàn)機(jī)制不一樣,所以并不是真正改變了對(duì)象的內(nèi)存結(jié)構(gòu)娄蔼。

這些成員變量是基本類型和指針類型怖喻,指針類型的個(gè)數(shù)決定了結(jié)構(gòu)體的大小,也就是實(shí)例變量決定著對(duì)象的內(nèi)存結(jié)構(gòu)岁诉,而運(yùn)行時(shí)改變指針指向的結(jié)構(gòu)體是不受影響的(如添加方法)

Non Fragile ivars(強(qiáng)壯的成員變量)

在C++中锚沸,成員變量的訪問會(huì)被編譯器轉(zhuǎn)成一條指令,用“對(duì)象地址”加“成員變量偏移值”即可訪問到成員變量的值涕癣,或許Objective-C2.0之前也是這樣的哗蜈。

上面說了類實(shí)例的結(jié)構(gòu)前标,父類的成員變量在前,子類的在后距潘。我們編譯后上傳到AppStore炼列,用戶下載到手機(jī)。當(dāng)蘋果發(fā)布新版本OSX SDK后音比。

例如俭尖,NSObject增加了兩個(gè)成員變量。如果沒有Non Fragile ivars特性洞翩,我們的代碼將無法正常運(yùn)行稽犁。我們的類繼承自NSObject,編譯時(shí)骚亿,類成員變量的已經(jīng)確定已亥。當(dāng)根類增添成員變量,我們的類的成員變量和基類的內(nèi)存區(qū)域重疊了来屠。此時(shí)虑椎,我們只能重新編譯我們的代碼,程序才能在新版本系統(tǒng)上運(yùn)行俱笛。

如果更悲催一點(diǎn)捆姜,如果我們使用了第三方提供的靜態(tài)庫(kù),我們就只能眼巴巴等著庫(kù)作者更新版本了迎膜。

Non Fragile ivars特性出場(chǎng)了娇未。在程序啟動(dòng)后,runtime加載MyObject類的時(shí)候星虹,通過計(jì)算基類的大小,runtime動(dòng)態(tài)調(diào)整了我們自定義類成員變量布局镊讼,把自定義類成員變量的位置向后移動(dòng)若干字節(jié)宽涌。于是我們的程序無需編譯,就能在新版本系統(tǒng)上運(yùn)行

那Non Fragile ivars是如何實(shí)現(xiàn)的呢蝶棋?最關(guān)鍵的點(diǎn)是卸亮,當(dāng)成員變量布局調(diào)整后,怎么能找到變量的新偏移位置呢玩裙?

沿著 objc_class的data()->ro->ivars 找下去兼贸,struct ivar_list_t 是類所有成員變量的定義列表。

struct ivar_list_t {

? ? ?uint32_t entsize;

? ? ? uint32_t count;

? ? ? ivar_t first;

};

通過first字段吃溅,可以取得類里任意一個(gè)類成員變量的定義溶诞。

struct ivar_t {

? ? ?int32_t *offset;

? ? ?const char *name;

? ? ?const char *type;

//...

};

offset,如果offset直接記錄著這個(gè)成員變量在對(duì)象中的偏移位置决侈,那么螺垢,runtime在發(fā)現(xiàn)基類大小變化時(shí),通過修改offset值,來更新子類成員變量的偏移值枉圃。那Objective-C中獲取對(duì)象的第N個(gè)成員變量偏移位置就需要這樣一長(zhǎng)串代碼:

*((&obj->isa.cls->data()->ro->ivars->first)[N]->offset)

這么多次尋址功茴,看起來很可怕吧。每個(gè)成員變量都這樣訪問的話孽亲,性能一定無法接受坎穿。看看編譯器到底是如何實(shí)現(xiàn)的吧返劲,我們祭出LLVM

@interface MyClass : NSError {

@public

? ? ? int myInt;

}

@end

@implementation MyClass

@end

int main()

{

? ? ?MyClass *obj = [[MyClass alloc] init];

? ? ? obj->myInt = 42;

}

obj->myInt = 42;

通過clang玲昧,我們看到這句代碼被轉(zhuǎn)為:

int32_t g_ivar_MyClass_myInt = 40;??// 全局變量

*(int32_t *)((uint8_t *)obj + g_ivar_MyClass_myInt) = 42;

兩條CPU指令搞定,根本不需要一長(zhǎng)串的指針調(diào)用旭等。LLVM為每個(gè)類的每個(gè)成員變量都分配了一個(gè)全局變量酌呆,用于存儲(chǔ)該成員變量的偏移值。

這也就是為什么結(jié)構(gòu)體中 ivar_t.offset 用int指針來存儲(chǔ)偏移值搔耕,而不是直接放一個(gè)int的原因隙袁。在這個(gè)設(shè)計(jì)中,真正存放偏移值的地址是固定不變的弃榨,在編譯時(shí)就確定了下來菩收。因此才能用區(qū)區(qū)2條指令搞定動(dòng)態(tài)布局的成員變量。

有了這種靈活而高效的尋址方式鲸睛,那runtime是在什么時(shí)候調(diào)整成員變量偏移值的呢娜饵?在編譯時(shí),LLVM計(jì)算出基類NSError對(duì)象的大小為40字節(jié)官辈,然后記錄在MyClass的類定義中箱舞。在編譯后的可執(zhí)行程序中,寫死了“40”這個(gè)魔術(shù)數(shù)字拳亿,記錄了在此次編譯時(shí)MyClass基類的大小晴股。

class_ro_t class_ro_MyClass = {

? ? ? .instanceStart = 40,?

? ? ? .instanceSize = 48,

//...

}

現(xiàn)在假如蘋果發(fā)布了OSX 11 SDK,NSError類大小增加到48字節(jié)肺魁。當(dāng)我們的程序啟動(dòng)后电湘,runtime加載MyClass類定義的時(shí)候,發(fā)現(xiàn)基類的真實(shí)大小和MyClass的instanceStart不相符鹅经,得知基類的大小發(fā)生了改變寂呛。

于是runtime遍歷MyClass的所有成員變量定義,將offset指向的值增加8瘾晃。具體的實(shí)現(xiàn)代碼在runtime/objc-runtime-new.mm的moveIvars()函數(shù)中贷痪。

并且,MyClass類定義的instanceSize也要增加8蹦误。這樣runtime在創(chuàng)建MyClass對(duì)象的時(shí)候呢诬,能分配出正確大小的內(nèi)存塊

在博客的結(jié)尾涌哲,又提到了,為什么無法在運(yùn)行時(shí)為類添加成員變量尚镰?

上面說過實(shí)例變量影響著對(duì)象的內(nèi)存結(jié)構(gòu)體阀圾,其實(shí)不但影響著當(dāng)前類的實(shí)例內(nèi)存,還影響著子類實(shí)例的內(nèi)存狗唉。為基類動(dòng)態(tài)增加成員變量會(huì)導(dǎo)致所有已創(chuàng)建出的子類實(shí)例都無法使用初烘。

那為什么runtime允許動(dòng)態(tài)添加方法和屬性,而不會(huì)引發(fā)問題呢分俯?

因?yàn)榉椒ê蛯傩圆⒉弧皩儆凇鳖悓?shí)例肾筐,而成員變量“屬于”類實(shí)例。我們所說的“類實(shí)例”概念缸剪,指的是一塊內(nèi)存區(qū)域吗铐,包含了isa指針和所有的成員變量。所以假如允許動(dòng)態(tài)修改類成員變量布局杏节,已經(jīng)創(chuàng)建出的類實(shí)例就不符合類定義了唬渗,變成了無效對(duì)象。但方法定義是在objc_class中管理的奋渔,不管如何增刪類方法镊逝,都不影響類實(shí)例的內(nèi)存布局,已經(jīng)創(chuàng)建出的類實(shí)例仍然可正常使用嫉鲸。

上面說的類實(shí)例就是撑蒜,我們說的對(duì)象在內(nèi)存中的結(jié)構(gòu)。另外上面提到了 obj->isa.cls->data()->ro 前面說過 ro 存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性玄渗、方法以及遵循的協(xié)議座菠。在運(yùn)行期間就不能改變了(只讀)

總結(jié)

1 在Objective-C,通過 -> 操作符藤树,操作成員變量時(shí)辈灼,不是C語(yǔ)言指針操作,通過clang 可以發(fā)現(xiàn)

2 程序啟動(dòng)后也榄,runtime加載MyClass類定義的時(shí)候,比較

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末司志,一起剝皮案震驚了整個(gè)濱河市甜紫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骂远,老刑警劉巖囚霸,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異激才,居然都是意外死亡拓型,警方通過查閱死者的電腦和手機(jī)额嘿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劣挫,“玉大人册养,你說我怎么就攤上這事⊙构蹋” “怎么了球拦?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)帐我。 經(jīng)常有香客問我坎炼,道長(zhǎng),這世上最難降的妖魔是什么拦键? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任谣光,我火速辦了婚禮,結(jié)果婚禮上芬为,老公的妹妹穿的比我還像新娘萄金。我一直安慰自己,他們只是感情好碳柱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布捡絮。 她就那樣靜靜地躺著,像睡著了一般莲镣。 火紅的嫁衣襯著肌膚如雪福稳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天瑞侮,我揣著相機(jī)與錄音的圆,去河邊找鬼。 笑死半火,一個(gè)胖子當(dāng)著我的面吹牛越妈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钮糖,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼梅掠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了店归?” 一聲冷哼從身側(cè)響起阎抒,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎消痛,沒想到半個(gè)月后且叁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秩伞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年逞带,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欺矫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡展氓,死狀恐怖穆趴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情带饱,我是刑警寧澤毡代,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站勺疼,受9級(jí)特大地震影響教寂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜执庐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一酪耕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轨淌,春花似錦迂烁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至躏结,卻和暖如春却盘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳拴。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工黄橘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屈溉。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓塞关,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親子巾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帆赢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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