ARC 與 Memory Leak:為什麼 Swift 還是會記憶體洩漏?
很多人第一次學 Swift 時,都會以為:
Swift 有 ARC,所以不會 memory leak。
但實際上,只要你做過中大型 iOS 專案,你很快就會發現:
Swift 不但會 leak,而且很容易 leak。
甚至很多 crash、效能問題、畫面卡頓、ViewController 無法釋放,本質上都跟:
memory ownership有關。
而 ARC 這題在面試真正想考的,也從來不是:
- strong / weak / unowned 的定義
- retain count 怎麼加減
而是:
你是否理解「物件的生命週期(lifecycle)」與「所有權(ownership)」。
1. ARC 到底是什麼?
ARC(Automatic Reference Counting)本質上是一套:
自動管理 reference memory 的機制
它的核心概念其實只有一句話:
只要還有人持有你,你就不能被釋放例如:
class User {
deinit {
print("deinit")
}
}
var user: User? = User()
user = nil
結果:
deinit因為:
- reference count = 1 → 建立時
- reference count = 0 → user = nil
所以 ARC 幫你釋放記憶體。
2. 真正的問題:ARC 不知道「你該不該互相持有」
ARC 很聰明。
但它不知道:
這兩個 object 到底是不是應該互相擁有
這就是 memory leak 的起點。
3. 最經典的 Retain Cycle
class Person {
var apartment: Apartment?
}
class Apartment {
var tenant: Person?
}
建立:
var wayne: Person? = Person()
var room: Apartment? = Apartment()
wayne?.apartment = room
room?.tenant = wayne
看起來沒問題。
但:
wayne = nil
room = nil
你會發現:
deinit 沒被呼叫因為:
Person 強持有 Apartment
Apartment 又強持有 Person
形成:
Retain Cycle4. ARC 不會幫你解決設計錯誤
這是很多初學者最大的誤解。
ARC 的工作不是:
判斷誰該活著ARC 的工作只是:
計算還有沒有人持有你所以如果你設計成:
大家互相強引用ARC 只會覺得:
那你們就一起永生吧5. 為什麼 Closure 是 Memory Leak 最大來源?
因為 Closure 本質上也是:
Reference Type而且它會:
捕獲(capture)外部變數看這段:
class ViewModel {
var onUpdate: (() -> Void)?
func fetch() {
onUpdate = {
self.reload()
}
}
func reload() {
print("reload")
}
deinit {
print("deinit")
}
}
問題在哪?
其實是:
self 強持有 closure
closure 又強持有 self
變成:
self → closure → self這是 iOS 最常見的 leak。
6. weak self 真正的意義
很多人背:
[weak self]
但不知道原因。
真正的原因是:
你在告訴 closure:
不要擁有我改成:
onUpdate = { [weak self] in
self?.reload()
}
現在:
- closure 不再強持有 self
- retain cycle 被打破
所以:
ViewModel 可以正常釋放7. weak vs unowned 到底差在哪?
這題超常考。
但大多數人只會背:
- weak 是 optional
- unowned 不是 optional
真正的差異其實是:
weak: 可以變 nil, 安全, 適合生命週期不確定
unowned: 假設一定存在, 效能略高, 適合生命週期綁定
weak
weak var delegate: MyDelegate?
意思是:
對方可能先死掉
所以要 optional。
unowned
unowned let owner: Owner
意思是:
我相信 owner 一定活著
如果 owner 已經釋放:
直接 crash8. Delegate 為什麼幾乎都要 weak?
這題其實在考:
誰擁有誰?
通常:
ViewController 擁有 Child所以:
Child 不應該反過來強持有 ViewController否則:
雙向 ownership就 leak 了。
9. SwiftUI 為什麼 leak 問題比較少?
因為 SwiftUI 的核心是:
Value-driven UI
View 幾乎都是:
struct所以:
- 不存在 retain cycle
- 不共享 reference
- 沒有 object graph
真正容易 leak 的地方反而是:
- ObservableObject
- async task
- timer
- notification
- Combine subscription
因為這些:
又回到 Reference World10. Async/Await 時代的新 Leak
很多人以為:
用了 async/await 就安全
其實沒有。
例如:
Task {
await self.fetch()
}
Task 會:
強持有 self如果:
- task 很久
- view 已離開
- task 沒取消
就可能:
ViewModel 永遠不釋放這也是為什麼現在越來越強調:
- Task cancellation
- structured concurrency
- actor isolation
11. Memory Leak 真正恐怖的地方
Leak 最可怕的不是:
記憶體變多而是:
舊狀態還活著例如:
- 舊 ViewModel 沒釋放
- 舊 observer 還在
- 舊 network callback 還在
- 舊 state 還在更新 UI
最後你會看到:
「明明畫面都關掉了,為什麼還在跑?」12. 面試真正想測的是什麼?
ARC 這題真正考的是:
你有沒有 ownership 思維
也就是:
- 誰該擁有誰
- 誰該活多久
- 誰該負責 lifecycle
- 哪裡可能產生循環引用
真正成熟的工程師,不會只想:
怎麼避免 leak而是會先想:
ownership 應該怎麼設計結論
ARC 從來不是:
自動記憶體管理它真正管理的是:
Reference Ownership而 Memory Leak 本質上也不是 bug。
它通常代表:
你的 object relationship 設計出了問題。
延伸思考
當你開始深入:
- SwiftUI
- Combine
- Async/Await
- Actor
- TCA
你會發現:
Swift 其實正在慢慢減少:
共享 reference state因為:
Shared mutable state 幾乎就是 complexity 的來源。
而 ARC,只是讓你開始看見這件事的入口而已。














