在 iOS 應用開發中,有效的內存管理是確保應用穩定運行的關鍵。一個常見但又經常被忽視的問題是 Memory Leak,它指的是當應用分配的記憶體在不再需要時未能釋放,導致應用使用的記憶體量持續增加。
這不僅會耗盡系統資源,還可能導致應用性能下降,甚至崩潰。
本文將探討 iOS 開發中產生 Memory Leak 的原因及其解決方法,特別是通過理解 strong reference cycles 和 closures capture list 的管理來避免這一問題。
Strong Reference Cycles 的挑戰與解決方案
Strong Reference Cycles 發生時,兩個或多個 objects 互相保持 strong reference,導致它們無法被釋放。解決這一問題的關鍵在於使用 weak
或 unowned
修飾 references。
Weak 與 Unowned 的選擇
Weak References:被 weak
修飾的 instance object 不會增加 retain count,這意味著它們是可選的(optional),可能為 nil
。這適用於那些可能變為 nil
的 instance object,如 delegate 或 dataSource,使用 weak
保持它們不會造成 strong reference cycles。
Unowned References:與 weak
不同,unowned
修飾的 instance object 同樣不會增加 retain count,但它們不能是可選的,這意味著這些 objects 應始終存在,不可能為 nil
。
如果一個 unowned
reference 的 object 被釋放,再次訪問它會導致應用崩潰。因此,unowned
適用於那些生命週期一致的 instance objects。
unowned 本質上是因為 optional 所產生出來的產物,因為相較於訪問那些 weak 修飾的 instance object,系統不用再做 unwrapped 的動作,所以在效率上會更高!
class Department {
var employeeAdded: ((String) -> Void)?
func addEmployee() {
employeeAdded?("Employee added!")
}
}
class Person {
var department: Department
init(department: Department) {
// set department's employeeAdded closure
// we can use unowned here, cause when closure was called
// self(Person instance object) must be existed
department.employeeAdded = { [unowned self] message in
print(message)
}
}
}
Closures Capture List
在 Swift 中,閉包(closures)可能捕獲並保持對其內部使用的外部變量的強引用。
為了避免因此產生的 strong reference cycles,可以在 closure 的 capture list 中使用 [weak xxx]
或 [unowned xxx]
同時也可以釐清 local variable, global variable, static variable 是如何被 closure 所捕獲的
有哪些檢測 memory leak 的方式
- 簡單的 leak 可以透過 UML diagram 看出
2. 可以使用 Xcode 內建的 Instruments 工具 (Leak 和 Allocation 工具) 來幫找到應用中的記憶體洩漏點