面試的時(shí)候幾乎都會(huì)被問(wèn), 是個(gè)比較大的問(wèn)題. 整理了一些可以聊的點(diǎn).
引用計(jì)數(shù)器, ARC 和 MRC
引用計(jì)數(shù)器: 通過(guò)引用計(jì)數(shù)決定一個(gè)引用是否需要釋放.
ARC: 由編譯器幫忙完成引用計(jì)數(shù)的增減.
MRC: 由開(kāi)發(fā)者手動(dòng)完成引用計(jì)數(shù)的增減.
Tagged Pointer
由于64位 CPU 的出現(xiàn), 部分類型如 Int 型的內(nèi)存占用會(huì)翻倍(值大小翻倍), 而一個(gè)存儲(chǔ)了 Int 型數(shù)據(jù)的 NSNumber 對(duì)象則從原來(lái)的8字節(jié)增加到16字節(jié). (指針位數(shù)翻倍).
Tagged Pointer 的出現(xiàn)就是為了針對(duì)這類情況, 對(duì)指針的內(nèi)存占用進(jìn)行壓縮.(比如一個(gè) NSNumber 存儲(chǔ)了一個(gè)值為1024的 Int 型, Tagged Pointer的內(nèi)存地址值會(huì)完整包含1024, 而不需要額外的內(nèi)存去存儲(chǔ)1024這個(gè)數(shù)據(jù)).
NONPOINTER_ISA
64位系統(tǒng)下的優(yōu)化指針, 攜帶內(nèi)容除了 對(duì)象內(nèi)存地址, 還有 是否優(yōu)化指針標(biāo)記/引用計(jì)數(shù)/是否有弱引用指向/是否正在釋放等信息
AutoReleasePool
干預(yù) ARC 環(huán)境下引用對(duì)象的 release 時(shí)機(jī), 使它在運(yùn)行語(yǔ)句離開(kāi)AutoReleasePool后執(zhí)行.
引用表SideTables(強(qiáng)/弱引用)
弱引用不會(huì)對(duì)指向?qū)ο笤斐蓃etain, runtime 維護(hù)了一個(gè)被稱為 SideTables 的 HashMap<Int, SideTable> 用于管理內(nèi)存地址的引用計(jì)數(shù)和弱引用, 這個(gè) HashMap 的 Key 就是引用的地址了, 而 Value 就是一個(gè)用于管理這個(gè)內(nèi)存地址對(duì)應(yīng)的引用的類SideTable.
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
// Address-ordered lock discipline for a pair of side tables.
template<bool HaveOld, bool HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<bool HaveOld, bool HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
SideTable結(jié)構(gòu)中有:
- 自旋鎖
spinlock_t, 在操作SideTable的時(shí)候上鎖.(為了可以同時(shí)對(duì)多個(gè)地址進(jìn)行操作, 使用分離鎖) - 表
RefcountMap, 管理內(nèi)存地址的引用計(jì)數(shù). - 表
weak_table_t, 管理內(nèi)存地址的弱引用.
對(duì)象的內(nèi)存管理操作
- 當(dāng)聲明一個(gè)強(qiáng)引用
ref指向?qū)ο?code>instance的時(shí)候:
- 會(huì)在
SideTables中找到對(duì)應(yīng)instance內(nèi)存地址的SideTable, 操作RefcountMap, 根據(jù)情況(是否已經(jīng)到達(dá)上限)增加引用計(jì)數(shù).
- 當(dāng)聲明一個(gè)弱引用
weak_ref指向某個(gè)對(duì)象instance的時(shí)候:
- 會(huì)生成一個(gè)新的引用
weak_ref, 指向這個(gè)對(duì)象的地址, 引用weak_ref會(huì)被放進(jìn)SideTables里對(duì)應(yīng)instance內(nèi)存地址的弱引用表weak_table_t中. 這個(gè)過(guò)程中, 對(duì)象weak_ref的引用計(jì)數(shù)器不會(huì)增加. - 操作對(duì)應(yīng)
instance的RefcountMap, 標(biāo)記為有弱引用指向.
- 當(dāng)一個(gè)強(qiáng)引用
ref不再指向?qū)ο?instance的時(shí)候:
- 查找對(duì)應(yīng)
instance的RefcountMap, 根據(jù)情況(是否正在釋放)減少引用計(jì)數(shù). - 如果
instance引用計(jì)數(shù)已經(jīng)為0, 則開(kāi)始對(duì)instance進(jìn)行釋放.
- 當(dāng)對(duì)象
instance要被釋放的時(shí)候:
- 操作對(duì)應(yīng)的
RefcountMap, 標(biāo)記目標(biāo)地址正在被釋放. - 根據(jù)情況(是否標(biāo)記為有弱引用指向)在引用表
weak_table_t中找到指向instance的所有弱引用, 把這些引用設(shè)置成nil.
內(nèi)存對(duì)齊
iOS 中數(shù)據(jù)結(jié)構(gòu)的內(nèi)存占用遵循以下規(guī)則
- 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第
一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要
從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說(shuō)是數(shù)組,
結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(比如int為4字節(jié),則要從4的整數(shù)倍地址開(kāi)始存
儲(chǔ)。- 結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ).(struct a里存有struct b,b
里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開(kāi)始存儲(chǔ).)- 收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大
成員的整數(shù)倍.不足的要補(bǔ)?。
值類型和引用類型
值類型: 拷貝數(shù)據(jù), 不需要考慮多線程操作沖突.
引用型: 拷貝指針地址, 需要考慮內(nèi)存釋放.
堆和棧
堆的容量大, 存放引用類型, 內(nèi)存由開(kāi)發(fā)者自己申請(qǐng)/釋放, 速度相對(duì)于棧更慢.
棧的容量有限, 存放值類型, 內(nèi)存由系統(tǒng)管理, 操作速度快.
深拷貝和淺拷貝
深拷貝: 開(kāi)辟新的內(nèi)存空間, 創(chuàng)建一個(gè)內(nèi)容和被拷貝對(duì)象一樣的新對(duì)象.
淺拷貝: 只是拷貝一份被拷貝對(duì)象的內(nèi)存地址, 相當(dāng)于創(chuàng)建一個(gè)指向被拷貝對(duì)象的指針.
函數(shù)派發(fā)
函數(shù)派發(fā)有3種:
- 靜態(tài)派發(fā)
- 編譯期間調(diào)用的函數(shù)必須已經(jīng)實(shí)現(xiàn).
- 在編譯時(shí)就決定了調(diào)用哪個(gè)函數(shù), 運(yùn)行時(shí)不可更改.
- 調(diào)用性能最好(編譯時(shí)可以針對(duì)優(yōu)化).
- 函數(shù)表派發(fā)
- 內(nèi)存里, 對(duì)每一個(gè)
class建立一個(gè)函數(shù)表, 用于存儲(chǔ)這個(gè)類的函數(shù)所在內(nèi)存位置. - 子類函數(shù)表中包含:
- 所有從父類中繼承的函數(shù), 函數(shù)的內(nèi)存地址和父類中相同函數(shù)的地址相同.
- 子類新增的函數(shù).
- 在子類的函數(shù)表里, 子類重寫(xiě)的函數(shù)會(huì)替換父類的函數(shù)(使用新的內(nèi)存地址).
- 調(diào)用性能弱于靜態(tài)派發(fā)方式(需要操作指針在表中查找函數(shù)內(nèi)存地址).
- 消息派發(fā)
- 一個(gè)消息發(fā)送給一個(gè)對(duì)象的時(shí)候, runtime 會(huì)針對(duì)這個(gè)對(duì)象構(gòu)建一個(gè)樹(shù).
- 樹(shù)的每一層包含:
- 該類自己實(shí)現(xiàn)的函數(shù).
- 該類上一層父類的指針(如果存在).
- runtime 從樹(shù)的根部開(kāi)始查找消息名稱對(duì)應(yīng)的函數(shù), 如果找不到當(dāng)層就往下一層查找.
- 如果到樹(shù)的末端依然找不到對(duì)應(yīng)的函數(shù), 也沒(méi)有實(shí)現(xiàn)消息重定向, 就會(huì)報(bào)錯(cuò)導(dǎo)致程序崩潰.
- 在調(diào)用之前仍可以不確定(不實(shí)現(xiàn))具體執(zhí)行函數(shù), 但是調(diào)用的時(shí)候必須確定.
- 在運(yùn)行期間, 函數(shù)可以更換.
- 調(diào)用性能較差(每次調(diào)用都需要重新確定).
Swift靜態(tài)派發(fā)場(chǎng)景:
- 值類型的所有函數(shù)
-
static或者final修飾的函數(shù) -
extension內(nèi)聲明的非@objc或dynamic修飾函數(shù)
Swift函數(shù)表派發(fā)場(chǎng)景:
-
class內(nèi)聲明非@objc或dynamic修飾的函數(shù) -
protocol內(nèi)聲明非@objc或dynamic修飾的函數(shù)
Swift消息派發(fā)場(chǎng)景:
-
@objc修飾的函數(shù) -
dynamic修飾的函數(shù)
weak, __weak, __block, unowned
copy on write
參考:
Swift進(jìn)階之內(nèi)存模型和方法調(diào)度
iOS weak的底層實(shí)現(xiàn)
iOS-內(nèi)存對(duì)齊
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法
Method Dispatch in Swift
深入理解Tagged Pointer
NONPOINTER_ISA和散列表