內(nèi)存分配
堆
堆是完全二叉樹料仗,底層節(jié)點填充是按照從左到右的順序進(jìn)行,Swift的堆是通過雙向鏈表實現(xiàn)的意敛,由于堆可以reatin和release馅巷,所以很容易使內(nèi)存不連續(xù),采用鏈表的形式是為了將內(nèi)存連起來草姻,release通過鏈表來整合空間
Weak
Swift4.0以前是對象強引用技術(shù)為0后钓猬,看弱引用技術(shù)來決定是否保留釋放對象所占用內(nèi)存,如果引用計數(shù)不為0撩独,那么內(nèi)存還會保留原來的內(nèi)存敞曹,知道引用計數(shù)為0才會被清理掉,综膀,為了避免這總情況澳迫,新版Swift引入了SideTable,讓對象弱引用指針指向?qū)?yīng)的sidetable剧劝,這樣做的好處是僵尸對象長期占用內(nèi)存的情況橄登,不需要保留僵尸對象,只保留引用計數(shù)和指向源對象指針內(nèi)存占用空間小
棧
棧的結(jié)構(gòu)比較簡單只有push和pop,內(nèi)存上只需要維護(hù)棧末端指針即可示绊,
派發(fā)機制
Swift派發(fā)目的是為了讓cpu知道被調(diào)用函數(shù)在哪里锭部,Swift支持 直接派發(fā)、函數(shù)表派發(fā)面褐、消息機制派發(fā)這三種機制拌禾。
直接派發(fā)
C++使用的是直接派發(fā),這樣做的優(yōu)勢是調(diào)用指令少展哭,缺點是缺少動態(tài)性
函數(shù)表派發(fā)
Java使用的派發(fā)方式是函數(shù)表派發(fā)湃窍,通過Final修飾的方法為直接派發(fā),而函數(shù)表派發(fā)具有動態(tài)性匪傍,一個類會用數(shù)組來存儲函數(shù)指針您市,重寫父類函數(shù)會替代以前的函數(shù)
舉個例子
class Fish {
func swim() {}
func eat() {}
}
class FlyingFish: Finsh {
override func eat() {}
func fly() {}
}
編譯器會給Fish和FlyingFish分別創(chuàng)建函數(shù)表,在Fish函數(shù)里有swim和eat函數(shù)沒再FlyingFish函數(shù)里有父類Fish的Swim和覆蓋了父類eat方法的函數(shù)以及新的fly函數(shù)
函數(shù)被調(diào)用有點讀取對象的函數(shù)表役衡,再根據(jù)該函數(shù)的偏移量的導(dǎo)函數(shù)地址在跳轉(zhuǎn)到相應(yīng)地址去茵休,比直接派發(fā)慢
消息機制派發(fā)
消息機制派發(fā)是在運行時可以改變函數(shù)的行為,kvo是對這種機制的運用手蝎,oc使用的是動態(tài)派發(fā)機制榕莺,c用的是直接派發(fā),所以c的性能高棵介,Swift可以通過dynamic方式使其支持動態(tài)派發(fā)钉鸯,當(dāng)一個消息被派發(fā)程序就需要按照集成關(guān)系向上查找被調(diào)用的函數(shù),但是這樣做效率不高邮辽,所以通過緩存來提高效率唠雕,這樣查找性能就和函數(shù)派發(fā)差不多了
派發(fā)使用場景
值類型使用的是直接派發(fā)
類和協(xié)議的extension是直接派發(fā)
類和協(xié)議的初始化使用的是函數(shù)表派發(fā)
@objc extension 使用的是消息機制派發(fā)
派發(fā)方式如下所示
final: 讓類里的函數(shù)使用直接派發(fā),這樣該函數(shù)沒有動態(tài)性程序運行也無法讀取到這個函數(shù)
dynamic: 讓類里的函數(shù)使用消息機制轉(zhuǎn)發(fā)可以重載extension函數(shù)吨述,
Swift會在派發(fā)上做優(yōu)化岩睁,如果函數(shù)沒有重載那么會用直接派發(fā)方式,如果屬性綁定kvo锐极,那么他的get set 方法可能會被優(yōu)化成直接派發(fā)笙僚,從而導(dǎo)致kvo失效,所以需要加上dynamic來保證kvo有效灵再。
基本數(shù)據(jù)內(nèi)存管理/結(jié)構(gòu)體內(nèi)存管理
結(jié)構(gòu)體和基本數(shù)據(jù)類型編譯時就可以確定內(nèi)存大小肋层,所以程序運行時不需要額外內(nèi)存空間因此函數(shù)調(diào)用就是直接傳地址
內(nèi)存對齊
操作系統(tǒng)在編譯的過程中,會以結(jié)構(gòu)體中成員的最大值作為其對齊的值翎迁,這是因為操作系統(tǒng)在數(shù)據(jù)讀取的時候栋猖,其實并不是一個字節(jié)一個字節(jié)進(jìn)行讀取的,而是一段一段進(jìn)行讀取汪榔,我們假如是4bytes蒲拉。假如我們要讀取一個int,這個int是從第1位到第4位。那么讀取的時候會發(fā)生什么事情呢雌团?首先我們需要先讀第一塊數(shù)據(jù)燃领,然后讀取后三位的數(shù)據(jù)。接下來锦援,讀取第二塊數(shù)據(jù)猛蔽,然后只取第一位的數(shù)據(jù)。最后將兩次的數(shù)據(jù)組合起來灵寺,就是我們想要的一個數(shù)據(jù)曼库。對于操作系統(tǒng)來說,這種處理數(shù)據(jù)的方式并不是特別地高效略板。我們都知道毁枯,在計算機領(lǐng)域,有一個特別有名的優(yōu)化手段叮称,就是空間換時間种玛。我們通過內(nèi)存對齊,直接跳過部分空的字節(jié)颅拦,然后一次性讀取所需數(shù)據(jù)蒂誉。內(nèi)存對齊的規(guī)則如下教藻,基本類型的對齊值距帅,其實就是他們的sizeof值。而結(jié)構(gòu)體的對齊值括堤,就是結(jié)構(gòu)體中最大的對齊值碌秸。
那么,知道內(nèi)存對齊了之后悄窃,有什么作用呢讥电?假如我們的一個結(jié)構(gòu)體有1個int,兩個char, 那么不同的排列順序轧抗,會造成結(jié)構(gòu)體的大小不一致恩敌。
我們注意到,不同的排列順序最終會讓結(jié)構(gòu)體占用不同的內(nèi)存横媚。雖然這些只是非常小的細(xì)節(jié)纠炮,但是在開發(fā)過程中,假如我們能夠注意到這些細(xì)節(jié)灯蝴,必將事半功倍恢口。
class 內(nèi)存管理
類本身實在對堆上進(jìn)行分配的,在Heap上還需要保存class的Type信息穷躁,Type信息有個函數(shù)表耕肩,類的函數(shù)在派發(fā)的時候會按照函數(shù)表派發(fā),子類需要繼承父類時只要在type里記錄自己信息即可
協(xié)議/泛型 內(nèi)存管理
存儲在一個existential container容器中,該容器的大致結(jié)構(gòu)是{ heapObject, metadata, PWT }