類(lèi)的內(nèi)存結(jié)構(gòu)優(yōu)化

WWDC2020對(duì)runtime的優(yōu)化

  • 視頻的觀看地址:https://developer.apple.com/videos/play/wwdc2020/10163/ (最好用Safari瀏覽器打開(kāi))
  • LLVM源碼地址:https://github.com/apple/llvm-project
    看完視頻后總結(jié):本次改動(dòng)不需要改動(dòng)任何代碼腕巡,也不用學(xué)習(xí)新的API鳞上,這次主要是runtime關(guān)于內(nèi)存的優(yōu)化史隆。在這種環(huán)境下我們不用改APP也會(huì)運(yùn)行得比之前更快更高效。

類(lèi)的數(shù)據(jù)結(jié)構(gòu)

《類(lèi)的探究分析》一文中就詳細(xì)地解讀了類(lèi)的結(jié)構(gòu)。在APP編譯成二進(jìn)制文件中搏予,類(lèi)的數(shù)據(jù)結(jié)構(gòu)發(fā)生了變化点把,其中包含了最常被訪問(wèn)的信息,指向元類(lèi)幌衣、父類(lèi)和方法緩存的指針矾削。以下是類(lèi)的數(shù)據(jù)結(jié)構(gòu)圖:

類(lèi)的數(shù)據(jù)結(jié)構(gòu)

Clean Memory 和 Dirty Memory的區(qū)別

Clean Memory

Clean Memory指的是在程序運(yùn)行中不會(huì)發(fā)生改變的內(nèi)存class_ro_t就是屬于Clean Memory的豁护,class_ro_t`內(nèi)存圖解:

class_ro_t數(shù)據(jù)結(jié)構(gòu)

  • clean memory 加載后不會(huì)發(fā)生改變的內(nèi)存
  • class_ro_t 就屬于clean memory哼凯,因?yàn)樗侵蛔x的,不會(huì)對(duì)齊內(nèi)存進(jìn)行修改
  • clean memory 是可以進(jìn)行移除的楚里,從而節(jié)省更多的內(nèi)存空間挡逼,因?yàn)槿绻阌行枰?code>clean memory,系統(tǒng)可以從磁盤(pán)中重新加載

Dirty Memory

Dirty Memory指的是在程序運(yùn)行中會(huì)發(fā)生改變的內(nèi)存腻豌,也就是我們俗稱(chēng)的臟數(shù)據(jù)家坎。類(lèi)的結(jié)構(gòu)一經(jīng)使用就會(huì)變成Dirty Memory吝梅,因?yàn)檫\(yùn)行時(shí)會(huì)向它寫(xiě)入新的數(shù)據(jù)。例如往類(lèi)中添加方法又或者加載類(lèi)的子類(lèi)父類(lèi)苏携,這里指的是class_rw_t。class_rw_t內(nèi)存圖解如下:

class_rw_t數(shù)據(jù)結(jié)構(gòu)

  • Dirty Memory是這個(gè)類(lèi)被分成兩部分的原因,可以保持類(lèi)加載后不會(huì)發(fā)生更改的數(shù)據(jù)越多越好装蓬,通過(guò)分離永遠(yuǎn)不會(huì)更改的數(shù)據(jù),可以把大量的類(lèi)數(shù)據(jù)存儲(chǔ)為Clean Memory牍帚。
  • class_rw_t(讀寫(xiě)):類(lèi)在加載的時(shí)候,屬性(properties)暗赶、協(xié)議(prococols)方法(methods)會(huì)被運(yùn)行時(shí)動(dòng)態(tài)的添加鄙币,也可以動(dòng)態(tài)的修改(Method Swizzling)。所以類(lèi)需要保存在class_rw_t中十嘿。
  • First Subclass岳锁、Next Subling Class:包含了運(yùn)行時(shí)才會(huì)生成的信息First Subclass、Next Subling Class唇聘,所有的類(lèi)都會(huì)變成一個(gè)樹(shù)狀結(jié)構(gòu)柱搜,就是通過(guò)First SubclassNext Subling Class指針實(shí)現(xiàn)的,它允許運(yùn)行時(shí)遍歷當(dāng)前使用的所有類(lèi)
  • Demangled Name:這個(gè)字段使用的頻率是比較少的宪肖,swift中才會(huì)使用健爬。

總結(jié):

dirty memory要比clean memory更有價(jià)值而且要多娜遵,只要進(jìn)行運(yùn)行它就必須一直存在,通過(guò)分離出那些不會(huì)被改變的數(shù)據(jù)慨仿,可以把大部分的類(lèi)數(shù)據(jù)存儲(chǔ)為clean memory纳胧,這樣才能不斷的提高程序的性能。

class_rw_t 的優(yōu)化

dirty memory在類(lèi)第一次加載的時(shí)候就一直存在万皿,runtime會(huì)為它分配額外的內(nèi)存。運(yùn)行時(shí)分配的存儲(chǔ)容量時(shí)class_rw_t用于讀取-編寫(xiě)數(shù)據(jù)牢硅,但是dirty memory中仍然存在著比較多的clean memory减余,為了提高空間的利用率,拆分出更多的clean memory休里,減少dirty memory容量是比奴可少的赃承。
第一步:拆分出class_ro_t悴侵,即運(yùn)行時(shí)不被修改的內(nèi)存可免。如下圖:

提取lass_ro_t

注意:由上圖發(fā)現(xiàn)一個(gè)疑點(diǎn),為什么方法捉撮,屬性在class_ro_t中時(shí)妇垢,class_rw_t還要有方法闯估,屬性呢?

  • 屬性和方法在運(yùn)行時(shí)中有可能會(huì)發(fā)生更改骑素,這需要放在class_rw_t中刚夺。
  • 在類(lèi)加載的時(shí)候,可以往類(lèi)中添加屬性和方法阳距。
  • class_ro_t只是可讀的筐摘,需要放在class_rw_t中跟蹤類(lèi)的相關(guān)信息。
    第二步:拆分class_rw_t圃酵,提取其中的clean memory
    在讀取-編寫(xiě)屬性和方法的時(shí)候馍管,只有10%的類(lèi)都需要修改或者添加的,那么90%類(lèi)可以說(shuō)是不被修改的捌锭,那么就可以對(duì)class_rw_t進(jìn)行拆分罗捎,拆分如下:
    class_rw_t拆分

    這樣的話(huà)class_rw_t的大小就會(huì)減少一半桨菜,對(duì)于真的用到了被拆分出去的數(shù)據(jù)的時(shí)候,可以使用擴(kuò)展(extension)來(lái)完成這些泻红,添加到類(lèi)中供其使用(大約90%的類(lèi)不需要這個(gè)擴(kuò)展)如下圖:
    優(yōu)化后的結(jié)構(gòu)

總結(jié)

  • 當(dāng)有類(lèi)使用了category的時(shí)候谊路,那么此時(shí)的類(lèi)就有了class_rw_t的結(jié)構(gòu)菩彬,如果未使用分類(lèi)挤巡,那么類(lèi)就是一個(gè)單純的class_ro_t的結(jié)構(gòu)。
  • 類(lèi)結(jié)構(gòu)的優(yōu)化其實(shí)最要是分離出class_ro_tclass_rw_t優(yōu)化喉恋,其實(shí)就是對(duì)class_rw_t不常用的部分進(jìn)行了剝離母廷。如果需要用到這部分就從擴(kuò)展記錄中分配一個(gè)琴昆,滑到類(lèi)中供其使用。

成員變量/實(shí)例變量和屬性的區(qū)別

《類(lèi)的探究分析》一文中提到了成員變量存放在class_ ro_t中抖拦,那么我們用過(guò)查找objc4源碼得出下圖:

實(shí)例變量存放位置

代碼層面探究:

@interface XXPerson : NSObject
{
    int hobby;                 //成員變量
    NSObject *objc;            //實(shí)例變量
}
@property (nonatomic,strong) NSString *name;                 //屬性
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic,assign) int age;
@end

通過(guò)xrun編譯成main.cpp文件态罪,查看底層代碼:

  • xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (手機(jī))
    main.cpp分析

    結(jié)論:
  • 屬性在編譯過(guò)程中自動(dòng)加上settergetter方法复颈。
  • 屬性在底層編譯階段會(huì)變成_方式的成員變量。

補(bǔ)充

官方類(lèi)型編碼

官方類(lèi)型編碼

注意:

  • 編碼文檔存放在Apple Documents地址
  • Objective-C 不支持long double類(lèi)型凿菩,@encode(long double)返回d帜讲,和double類(lèi)型的編碼值一樣舒帮。
    案例分析:
    案例分析

    setName(v24@0:8@16)分析結(jié)果:
  • vvoid陡叠,代表無(wú)返回值枉阵。
  • 24setName函數(shù)的占用字節(jié)數(shù)。
  • @:參數(shù)侦厚,id或者self拙徽。
  • 0:從0號(hào)位置開(kāi)始膘怕。
  • :SEL
  • 8:從8號(hào)位置開(kāi)始岛心。
  • @:參數(shù)忘古,setName
  • 16:從16號(hào)位置開(kāi)始送朱。

objc_setProperty與copy的關(guān)系

objc_setProperty方法相當(dāng)于一個(gè)中間層方法,主要是避免了每個(gè)類(lèi)都調(diào)用底層的objc_setProperty方法它改。當(dāng)用copy關(guān)鍵字修飾屬性時(shí)商乎,該屬性在編譯時(shí)候setter方法就會(huì)從定向到objc_setProperty方法鹉戚,不像其他屬性·setter·方法使用首地址+內(nèi)存偏移的方式找到方法實(shí)現(xiàn)。
示例代碼:

@interface XJPerson : NSObject
@property (nonatomic,copy) NSString *name;         //注意每個(gè)屬性的關(guān)鍵字
@property (nonatomic,strong) NSString *nickName;
@property (atomic,copy) NSString *address;
@property (atomic) NSString *school;
@end

編譯之后查看main.cpp遏餐,得到下圖:

main.cpp源碼結(jié)果

結(jié)論:

  • 使用copy關(guān)鍵字修飾的屬性底層setter方法重定向到objc_setProperty方法
  • 沒(méi)使用copy關(guān)鍵字修飾的屬性底層setter方法通過(guò)首地址+內(nèi)存偏移來(lái)尋找并實(shí)現(xiàn)失都。

LLVM驗(yàn)證對(duì)象屬性為copy時(shí)幸冻,setter方法的訪問(wèn)

驗(yàn)證流程圖:

LLVM驗(yàn)證流程圖

LLVM源碼流程:objc_setProperty -> getSetPropertyFn -> GetPropertySetFunction -> PropertyImplStrategy -> IsCopy(判斷copy關(guān)鍵字)
結(jié)論:無(wú)論屬性是否是原子性還是非原子性的洽损,用到copy關(guān)鍵字修飾的屬性setter方法底層都用objc_setProperty實(shí)現(xiàn),strong關(guān)鍵字無(wú)法通過(guò)最后得判斷流码,需要通過(guò)首地址+內(nèi)存偏移的方式實(shí)現(xiàn)延刘。

總結(jié):

底層代碼的分析需要很好的耐心碘赖,過(guò)程也是非常的枯燥。但是當(dāng)你弄明白了原理之后秘车,就會(huì)發(fā)現(xiàn)知識(shí)是環(huán)環(huán)相扣的劫哼。非常高興自己又多了一分收獲权烧,加油伤溉!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妻率,一起剝皮案震驚了整個(gè)濱河市宫静,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伏伯,老刑警劉巖捌袜,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虏等,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡候引,警方通過(guò)查閱死者的電腦和手機(jī)慕淡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)峰髓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)息尺,“玉大人搂誉,你說(shuō)我怎么就攤上這事〔⒓叮” “怎么了侮腹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵父阻,是天一觀的道長(zhǎng)望抽。 經(jīng)常有香客問(wèn)我履婉,道長(zhǎng)毁腿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任身害,我火速辦了婚禮草戈,結(jié)果婚禮上唐片,老公的妹妹穿的比我還像新娘。我一直安慰自己茧球,他們只是感情好星持,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布督暂。 她就那樣靜靜地躺著,像睡著了一般饥努。 火紅的嫁衣襯著肌膚如雪八回。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音管引,去河邊找鬼汉匙。 笑死生蚁,一個(gè)胖子當(dāng)著我的面吹牛戏自,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播志衣,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼念脯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼弯淘!你這毒婦竟也來(lái)了庐橙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤转培,失蹤者是張志新(化名)和其女友劉穎浆竭,沒(méi)想到半個(gè)月后邦泄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡易稠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了测萎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届巩。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恕汇,死狀恐怖或辖,靈堂內(nèi)的尸體忽然破棺而出颂暇,到底是詐尸還是另有隱情但惶,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布县爬,位于F島的核電站添谊,受9級(jí)特大地震影響斩狱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喊废,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望工闺。 院中可真熱鬧瓣蛀,春花似錦惋增、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)截歉。三九已至,卻和暖如春咸作,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墅诡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工毫胜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酵使,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓样屠,卻偏偏與公主長(zhǎng)得像缺脉,于是被迫代替她去往敵國(guó)和親攻礼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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