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)圖:
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)存圖解:
-
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)存圖解如下:
-
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 Subclass
和Next 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)存可免。如下圖:
注意:由上圖發(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)行拆分罗捎,拆分如下:
這樣的話(huà)class_rw_t
的大小就會(huì)減少一半桨菜,對(duì)于真的用到了被拆分出去的數(shù)據(jù)的時(shí)候,可以使用擴(kuò)展(extension)
來(lái)完成這些泻红,添加到類(lèi)中供其使用(大約90%的類(lèi)不需要這個(gè)擴(kuò)展)如下圖:
總結(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_t
和class_rw_t
優(yōu)化喉恋,其實(shí)就是對(duì)class_rw_t
不常用的部分進(jìn)行了剝離母廷。如果需要用到這部分就從擴(kuò)展
記錄中分配一個(gè)琴昆,滑到類(lèi)中供其使用。
成員變量/實(shí)例變量和屬性的區(qū)別
《類(lèi)的探究分析》一文中提到了成員變量存放在class_ ro_t
中抖拦,那么我們用過(guò)查找objc4源碼得出下圖:
代碼層面探究:
@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ī))
結(jié)論: - 屬性在編譯過(guò)程中自動(dòng)加上
setter
和gette
r方法复颈。 - 屬性在底層編譯階段會(huì)變成
_
方式的成員變量。
補(bǔ)充
官方類(lèi)型編碼
注意:
- 編碼文檔存放在Apple Documents地址
-
Objective-C
不支持long double
類(lèi)型凿菩,@encode(long double)
返回d
帜讲,和double
類(lèi)型的編碼值一樣舒帮。
案例分析:
setName(v24@0:8@16)
分析結(jié)果: -
v
:void
陡叠,代表無(wú)返回值枉阵。 -
24
:setName
函數(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
遏餐,得到下圖:
結(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源碼流程:
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)相扣的劫哼。非常高興自己又多了一分收獲权烧,加油伤溉!