Weak Reference — khái niệm, cú pháp và ứng dụng trong phát triển di động

Tác giả: IT Sectr Đã đăng: 2026-03-30 Thời gian đọc: 9 phút

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 Reference — tham chiếu không ảnh hưởng đến retain count của đối tượng; trở thành nil khi đối tượng được giải phóng
  • Khai báo — từ khóa weak trước var; kiểu luôn là tùy chọn (?)
  • Ứng dụng — delegate, closure, quan hệ cha-con để phá vỡ vòng giữ
  • An toàn — tự động đặt thành nil sau khi đối tượng được giải phóng (zeroing weak)
  • Khác biệt với unowned — weak trở thành nil và an toàn, unowned không trở thành nil và yêu cầu đảm bảo vòng đời

Weak Reference là gì?

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.

Cú pháp weak trong Swift và Objective-C

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.

Swift

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.

swift
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.

Objective-C

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
// 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.

Khi nào sử dụng tham chiếu yếu

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 (mẫu Delegate)

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.

Cha-Con với tham chiếu ngược

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ỏ.

Closure bất đồng 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ốngWeakStrong
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.

Weak vs Unowned: so sánh và tình huống

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.

Khác biệt

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.

Khi nào chọn weak

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.

Khi nào chọn unowned

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ã.

swift
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.

Hạn chế của tham chiếu yếu và cạm bẫy

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.

Hiệu suất của weak

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.

Weak không áp dụng được cho kiểu giá trị

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.

Weak trong đa luồng

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ộ.

swift
// Đ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.

UIView và Weak Outlet

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

Tham chiếu yếu có thể trỏ đến một đối tượng chưa được tạo không?

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.

Tại sao weak chỉ hoạt động với kiểu class?

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.

Weak ảnh hưởng đến hiệu suất trong vòng lặp như thế nào?

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 nào tham chiếu yếu có thể trở thành nil bất ngờ?

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.

Weak khác với __weak trong Objective-C như thế nào?

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 Reference — tham chiếu không sở hữu, không làm tăng retain count và tự động được đặt về không khi giải phóng
  • Cú phápweak var + kiểu tùy chọn; chỉ kiểu class và giao thức AnyObject
  • Zeroing weak — runtime đặt tất cả tham chiếu yếu đến đối tượng đã giải phóng về không, ngăn chặn con trỏ treo
  • Tình huống — delegate, cha-con với tham chiếu ngược, closure bất đồng bộ ([weak self])
  • Weak vs Unowned — weak trở thành nil (an toàn), unowned không trở thành nil (nguy cơ crash, nhưng không tùy chọn)
  • Hiệu suất — weak chậm hơn strong do tra cứu trong bảng runtime; cho đường dẫn nóng, sao chép thành strong
  • Khuyến nghị — nếu không chắc chắn về đảm bảo vòng đời, hãy chọn weak

Chú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.

Thảo luận dự án

Đọc thêm