
1) ARC nền tảng
- ARC (Automatic Reference Counting) quản lý vòng đời reference type (
class,actor,closure). - Mỗi tham chiếu mạnh tăng strong reference count. Khi count về 0 → deinit chạy → giải phóng.
- Retain cycle: hai hoặc nhiều thực thể giữ mạnh lẫn nhau nên count không về 0 ⇒ rò rỉ bộ nhớ.
final class A { var b: B?; deinit { print("A deinit") } }
final class B { var a: A?; deinit { print("B deinit") } }
var a: A? = A(); var b: B? = B()
a!.b = b; b!.a = a // strong ↔ strong → retain cycle
a = nil; b = nil // không deinit
2) Retain cycle: hai mô thức phổ biến
2.1 Object ↔ Object
- Hai thuộc tính
strongtham chiếu lẫn nhau. - Cách sửa: Một phía dùng
weak(hoặcunownednếu không bao giờ nil) theo quan hệ sở hữu hợp lý.
final class Parent { var child: Child? }
final class Child { weak var parent: Parent? } // tránh cycle
2.2 Closure ↔ Object
- Closure giữ mạnh
selftheo mặc định. Nếu object giữ closure như property, tạo vòng lặp.
final class Owner {
var handler: (() -> Void)?
func setup() {
handler = { self.doWork() } // giữ mạnh self → cycle nếu Owner giữ handler
}
func doWork() {}
}
- Cách sửa: dùng capture list
[weak self]hoặc[unowned self].
handler = { [weak self] in self?.doWork() } // an toàn nil
// hoặc
handler = { [unowned self] in self.doWork() } // không nil, crash nếu self đã giải phóng
3) weak vs unowned
| Thuộc tính | weak | unowned |
|---|---|---|
| Loại | Optional tham chiếu yếu | Tham chiếu yếu không optional |
| Tự động nil | Có | Không |
| An toàn | Cao, cần unwrap | Thấp hơn, crash nếu truy cập sau giải phóng |
| Dùng khi | Vòng tham chiếu có thể kết thúc trước | Vòng đời đối tượng được đảm bảo dài hơn closure/trỏ tới |
Quy tắc thực dụng:
- Mặc định dùng
[weak self]cho closure sống dài hoặc có khả năng outliveself(VD: network callback, timer, animation, Combine subscription). - Dùng
[unowned self]khi chứng minh đượcselfchắc chắn còn sống trong suốt thời gian closure chạy (VD: trongUIViewControllerthực thi tức thời trongviewDidLoadkhông lưu giữ, hoặc mối quan hệ chủ–tớ chặt chẽ).
Ví dụ so sánh:
// weak: an toàn, phải unwrap
service.fetch { [weak self] result in
guard let self = self else { return }
self.render(result)
}
// unowned: gọn, nhưng nguy hiểm nếu service giữ closure lâu hơn self
service.sync { [unowned self] data in
render(data)
}
4) @escaping là gì và liên hệ với retain cycle
@escapingáp cho tham số closure của hàm khi closure có thể được gọi sau khi hàm trả về. Khi đó, hàm thường lưu giữ closure (property, queue, completion list).- Closure non-escaping chỉ sống trong phạm vi hàm, compiler có thể tối ưu capture.
func loadNow(block: () -> Void) { block() } // non-escaping
func loadLater(block: @escaping () -> Void) { tasks.append(block) } // escaping
- Khi một object giữ escaping closure làm property, và closure capture mạnh
self, bạn có retain cycle.
final class VC: UIViewController {
private var completion: (() -> Void)?
func fetch() {
completion = { [weak self] in self?.updateUI() } // tránh cycle
api.request(completion: completion!)
}
}
Lưu ý: @escaping không tự gây rò rỉ. Nó cho phép vòng đời closure kéo dài, từ đó dễ phát sinh cycle nếu capture sai.
5) Mẫu dùng capture list
// 5.1 Chuẩn an toàn cho callback dời hạn
api.request { [weak self] data in
guard let self = self else { return }
self.render(data)
}
// 5.2 Tránh capture biến mạnh ngoài ý muốn
var count = 0
let block = { [count] in print(count) } // capture by value
// 5.3 Kết hợp nhiều mục
cache.load(key) { [weak self, unowned cache] value in
guard let self = self else { return }
self.apply(value); cache.touch(key)
}
6) Timer, NotificationCenter, Combine, async/await
Timer
class C {
var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.tick()
}
}
deinit { timer?.invalidate() }
}
NotificationCenter
class C {
var token: NSObjectProtocol?
func observe() {
token = NotificationCenter.default.addObserver(forName: .init("X"), object: nil, queue: .main) {
[weak self] _ in self?.handle()
}
}
deinit { if let t = token { NotificationCenter.default.removeObserver(t) } }
}
Combine
final class VM {
private var bag = Set<AnyCancellable>()
func bind() {
publisher
.receive(on: DispatchQueue.main)
.sink { [weak self] value in self?.apply(value) }
.store(in: &bag) // giải phóng khi VM deinit
}
}
async/await
final class VC: UIViewController {
func load() {
Task { [weak self] in
guard let self else { return }
let data = try await api.fetch()
self.render(data)
}
}
}
7) Debug và đo rò rỉ
- Instruments → Leaks / Allocations để phát hiện object không deinit.
- Thêm
deinit { print("ClassName deinit") }trong debug để kiểm chứng. - Dùng Memory Graph Debugger (Xcode) để thấy chuỗi tham chiếu.
Checklist nhanh:
- Controller không
deinitkhi dismiss? Kiểm tra Timer, Notification, Combine, Task. - Closure property có
[weak self]chưa? Có self → closure → self không? - Quan hệ cha–con đặt phía con
weakhayunownedđúng chưa?
8) Tình huống phỏng vấn
Q1. Phân biệt weak và unowned?
weak: optional, tự nil, an toàn hơn. Dùng khi đối tượng có thể biến mất trước.unowned: non-optional, không nil. Dùng khi đảm bảo vòng đời.
Q2. Khi nào cần @escaping?
- Khi callback chạy sau khi hàm trả về. Ví dụ network, lưu closure vào property.
Q3. Vì sao [weak self] thường dùng với network/animation?
- Vì chúng thực thi muộn, có thể vượt qua vòng đời
self. Tránh giữ mạnhself.
Q4. Có nên luôn dùng [weak self]?
- Không. Với closure nội bộ, thực thi tức thời, hoặc không lưu giữ, không cần. Lạm dụng gây rườm rà và che bug logic.
Q5. Cho ví dụ cycle và cách phá?
- Owner giữ closure property, closure capture mạnh self. Phá bằng
[weak self]hoặc không giữ closure nữa.
Q6. Dùng unowned khi nào an toàn?
- Khi chắc chắn
selfcòn sống trọn vòng đời closure. Nếu sai sẽ crash.
9) Mẫu anti-pattern và sửa
Anti-pattern:
class VC: UIViewController {
var onDone: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onDone = { self.dismiss(animated: true) } // giữ self mạnh
}
}
Sửa:
onDone = { [weak self] in self?.dismiss(animated: true) }
Anti-pattern:
class VM { var cb: ((Int) -> Void)? }
class View { let vm = VM()
init() {
vm.cb = { value in self.render(value) } // self ↔ vm nếu View giữ vm
}
}
Sửa:
vm.cb = { [weak self] value in self?.render(value) }
10) Kết luận nhanh
@escapingcho phép callback sống lâu → dễ tạo cycle.- Dùng
[weak self]theo mặc định cho closure dời hạn. Chỉ dùngunownedkhi chắc chắn vòng đời. - Kiểm tra bằng
deinit, Memory Graph, Leaks. - Thiết kế quan hệ sở hữu rõ ràng: cha sở hữu con mạnh, con tham chiếu cha yếu.
