Weak Reference (tham chiếu yếu) là một tham chiếu đến một đối tượng không làm tăng bộ đếm giữ của nó trong ARC. Theo Apple Swift Language Guide, 2026, tham chiếu yếu được khai báo với từ khóa weak và luôn là kiểu tùy chọn. Khi đối tượng được giải phóng, tất cả các tham chiếu yếu đến nó tự động được đặt thành nil, ngăn chặn con trỏ treo và biến tham chiếu yếu thành cơ chế an toàn để phá vỡ vòng giữ.
Những điểm chính
weak trước var; kiểu luôn là tùy chọn (?)Weak Reference là một tham chiếu không sở hữu đến một đối tượng trong ARC (Automatic Reference Counting). Không giống như tham chiếu mạnh, làm tăng retain count của đối tượng và đảm bảo vòng đời của nó, tham chiếu yếu cho phép đối tượng được giải phóng ngay cả khi nó vẫn đang được tham chiếu. Sau khi giải phóng, tham chiếu yếu tự động được đặt thành nil — điều này được gọi là zeroing weak.
Zeroing weak là một tính năng chính của runtime Swift và Objective-C. Khi bộ đếm tham chiếu của một đối tượng đạt đến không và đối tượng được giải phóng, runtime sẽ duyệt qua tất cả các tham chiếu yếu đến đối tượng này (được lưu trữ trong một bảng yếu đặc biệt) và đặt chúng thành nil. Điều này đảm bảo rằng việc truy cập vào bộ nhớ đã giải phóng (use-after-free) là không thể thông qua các tham chiếu yếu — bất kỳ lần đọc nào cũng trả về nil.
Theo Apple WWDC 2012 Session 406, các tham chiếu yếu zeroing đã loại bỏ toàn bộ một lớp lỗi crash liên quan đến con trỏ treo (dangling pointers), vốn phổ biến trong quản lý bộ nhớ thủ công (MRR). Trong MRR, tham chiếu yếu chỉ tồn tại dưới dạng __unsafe_unretained — chúng không được đặt về không, và truy cập vào một đối tượng đã giải phóng dẫn đến EXC_BAD_ACCESS.
Hãy xem cú pháp khai báo tham chiếu yếu trong cả hai ngôn ngữ của hệ sinh thái Apple. Mặc dù runtime được chia sẻ, cú pháp khác nhau, nhưng ngữ nghĩa là giống hệt nhau.
Trong Swift, tham chiếu yếu được khai báo với từ khóa weak trước var. Kiểu phải luôn là tùy chọn (Type?), vì tham chiếu có thể trở thành nil bất cứ lúc nào. Hằng số (let) không thể là weak — chỉ có biến.
class ViewController: UIViewController {
// weak properties: only var, only optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ closure không lưu trữ weak
// ⬆️ Lỗi: weak chỉ có thể áp dụng cho kiểu class, không phải closure
}
Quan trọng: weak chỉ áp dụng được cho các thể hiện của lớp (kiểu class), AnyObject và các giao thức kế thừa từ AnyObject. Struct, enum và closure không thể là weak — chúng là kiểu giá trị và không tham gia vào ARC.
Trong Objective-C, các thuộc tính yếu được khai báo bằng thuộc tính __weak hoặc bổ từ weak trong khai báo thuộc tính:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Biến weak cục bộ
__weak MyObject *weakRef = someStrongObject;
Runtime Objective-C cũng cung cấp zeroing weak, nhưng bổ sung chặn việc sử dụng weak với cấu trúc C và một số đối tượng Core Foundation. Cho những trường hợp này, __unsafe_unretained được sử dụng — không có zeroing.
Tham chiếu yếu không phải là giải pháp phổ quát, mà là công cụ cho các tình huống cụ thể. Sử dụng weak ở khắp mọi nơi dẫn đến sự phức tạp không cần thiết và làm giảm khả năng đọc. Hãy xem các tình huống sử dụng đúng.
Delegate — tình huống chính cho weak. Đối tượng sở hữu (ví dụ: UITableView) giữ một tham chiếu mạnh đến chính nó, trong khi delegate (UIViewController) không nên sở hữu bảng. Apple SDK đảm bảo rằng tất cả delegate và dataSource đều là weak. Cho các giao thức của riêng bạn, luôn sử dụng weak var delegate.
Khi một đối tượng con cần tham chiếu đến cha của nó (ví dụ: ChildViewController truy cập vào một coordinator), hãy sử dụng tham chiếu yếu. Cha sở hữu con (strong), con quan sát cha (weak) — vòng giữ được loại bỏ.
Capture list [weak self] — cách tiêu chuẩn để tránh vòng giữ trong các closure được lưu trữ dưới dạng thuộc tính của lớp. Nếu self có thể được giải phóng trước khi closure hoàn thành, weak self là bắt buộc.
| Tình huống | Weak | Strong |
|---|---|---|
| Delegate | ✅ Luôn weak | ❌ Vòng giữ |
| Cha → Con | ❌ Không cần (cha nên sở hữu) | ✅ Strong |
| Con → Cha | ✅ Weak | ❌ Vòng giữ |
| Callback bất đồng bộ | ✅ [weak self] | ❌ Nguy cơ vòng giữ |
| Liên kết chặt (owned) | ❌ unowned | ✅ Strong |
Nguyên tắc chung: nếu đối tượng A sở hữu B (A → B strong), thì B → A nên là weak hoặc unowned. Hướng của tham chiếu mạnh phải luôn từ chủ sở hữu đến cấp dưới.
Cả weak và unowned đều không làm tăng retain count, nhưng khác nhau về hành vi sau khi đối tượng được giải phóng. Sự lựa chọn giữa chúng là vấn đề đảm bảo vòng đời.
Weak: tự động trở thành nil, kiểu luôn là tùy chọn, yêu cầu mở gói trước khi sử dụng. An toàn — truy cập nil không gây crash.
Unowned: không trở thành nil, kiểu không tùy chọn. Nếu đối tượng được giải phóng, tham chiếu unowned trở thành con trỏ treo — truy cập vào nó gây ra crash runtime. Unowned giả định rằng đối tượng sống ít nhất bằng phía tham chiếu.
Chọn weak nếu: đối tượng có thể được giải phóng bất cứ lúc nào (delegate sau khi đóng màn hình), bạn không kiểm soát vòng đời của đối tượng, hoặc không chắc chắn về các đảm bảo. Weak là lựa chọn an toàn phổ quát.
Chọn unowned nếu: đối tượng được đảm bảo không bị giải phóng trước đối tượng tham chiếu (ví dụ: Customer → CreditCard, nơi thẻ không tồn tại nếu không có khách hàng). Unowned cung cấp API không tùy chọn mà không cần mở gói, thuận tiện hơn trong mã.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Quan hệ mạnh: Order sở hữu Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item không tồn tại nếu không có Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Ví dụ với weak: delegate không có đảm bảo vòng đời
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — delegate có thể biến mất
}
Trong ví dụ, Item sử dụng unowned vì một mục đặt hàng không thể tồn tại mà không có chính đơn hàng — đảm bảo vòng đời là chắc chắn. NetworkService sử dụng weak vì delegate (ví dụ: ViewController) có thể bị đóng và giải phóng bất cứ lúc nào.
Tham chiếu yếu là một công cụ mạnh mẽ, nhưng chúng có những hạn chế cần hiểu để sử dụng đúng trong phát triển iOS.
Tham chiếu yếu chậm hơn tham chiếu mạnh: mỗi lần truy cập, runtime kiểm tra xem đối tượng đã được giải phóng chưa (tra cứu trong bảng yếu). Trong phần lớn các tình huống, sự khác biệt là không đáng kể, nhưng trong các vòng lặp nóng với hàng triệu lượt truy cập, weak có thể trở thành nút cổ chai. Cho các tình huống tải cao, hãy sử dụng strong và tổ chức lại kiến trúc.
Struct, enum, tuple — các kiểu giá trị không tham gia vào ARC. Cố gắng khai báo weak struct dẫn đến lỗi biên dịch. Để lưu trữ tham chiếu yếu đến một kiểu giá trị, hãy sử dụng một wrapper trong kiểu lớp hoặc một closure.
Zeroing weak an toàn với luồng: nếu một đối tượng được giải phóng trên một luồng, tham chiếu yếu được đặt về không trên tất cả các luồng một cách nguyên tử. Tuy nhiên, khoảng cách giữa việc đọc tham chiếu yếu và giải tham chiếu có thể dẫn đến điều kiện tranh đua — đối tượng bị giải phóng giữa lúc lấy tham chiếu yếu và sử dụng nó. Giải pháp: bắt mạnh tham chiếu yếu vào một biến cục bộ.
// Điều kiện tranh đua với weak trong đa luồng
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf có thể là nil giữa kiểm tra và sử dụng
if weakSelf != nil {
weakSelf!.doSomething() // CRASH nếu trở thành nil
}
}
}
// ✅ Sửa: bắt mạnh trong khi sử dụng
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — tham chiếu mạnh cục bộ
}
}
Trong phiên bản an toàn, weak self được bắt, sau đó ngay lập tức được mở gói thành một biến mạnh cục bộ strongSelf. Nếu self vẫn còn sống, nó sẽ tiếp tục sống trong suốt thời gian của khối. Nếu không, guard được kích hoạt và mã không được thực thi. Cách diễn đạt này là mẫu tiêu chuẩn cho các closure bất đồng bộ trong Swift.
IBOutlet trong Interface Builder nên là weak vì hệ thống phân cấp view đã giữ một tham chiếu mạnh đến subview. Sao chép một tham chiếu mạnh trong bộ điều khiển không tạo ra vòng giữ nhưng là dư thừa. Tham chiếu yếu đến một outlet là khuyến nghị của Apple, mặc dù nhiều nhà phát triển sử dụng strong để đơn giản hóa mã.
Câu hỏi thường gặp
Không, weak chỉ có thể trỏ đến một đối tượng hiện có hoặc nil. Khi tạo một đối tượng mới, trước tiên bạn nhận được một tham chiếu mạnh (thông qua bộ khởi tạo), và chỉ sau đó bạn mới có thể gán một tham chiếu yếu. weak nil ở đầu là trạng thái bình thường.
Weak dựa trên ARC, chỉ quản lý các kiểu tham chiếu (class). Kiểu giá trị (struct, enum) được sao chép khi gán và không có retain count. Cho các mối quan hệ yếu với kiểu giá trị, hãy sử dụng closure hoặc wrapper trong một lớp với thuộc tính weak.
Mỗi lần truy cập vào tham chiếu yếu thực hiện một tra cứu trong bảng runtime. Trong một vòng lặp với hàng triệu lần lặp, điều này có thể chậm hơn 2–5 lần so với tham chiếu mạnh. Cho các đường dẫn nóng, hãy sao chép weak vào một biến mạnh cục bộ trước vòng lặp.
Khi tất cả các tham chiếu mạnh đến đối tượng bị mất — ở cuối phạm vi, khi gán lại thuộc tính, hoặc khi đóng màn hình. Trong môi trường đa luồng, điều này có thể xảy ra giữa hai dòng mã. Luôn kiểm tra tham chiếu yếu bằng guard let hoặc if let.
Giống hệt về mặt ngữ nghĩa: cả hai đều cung cấp zeroing weak. Khác biệt: Swift yêu cầu kiểu tùy chọn và var, Objective-C sử dụng bổ từ thuộc tính. Objective-C cũng hỗ trợ __unsafe_unretained — tham chiếu yếu không zeroing (rủi ro con trỏ treo).
Tổng kết
weak var + kiểu tùy chọn; chỉ kiểu class và giao thức AnyObjectChúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm