Strong Reference (tham chiếu mạnh): nó là gì, cơ chế hoạt động và ARC

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

Strong Reference (tham chiếu mạnh) là một cơ chế quản lý bộ nhớ tiêu chuẩn, trong đó một đối tượng vẫn nằm trong bộ nhớ miễn là có ít nhất một tham chiếu hoạt động trỏ đến nó. Không giống như tham chiếu yếu, tham chiếu mạnh làm tăng bộ đếm tham chiếu của đối tượng và ngăn chặn việc giải phóng tự động. Theo Apple Developer Documentation, ARC tự động quản lý vòng đời của các đối tượng trong Swift và Objective-C. Hiểu cách hoạt động của tham chiếu mạnh là rất quan trọng để ngăn ngừa rò rỉ bộ nhớ và các phụ thuộc vòng trong ứng dụng di động.

Ý chính

  • Strong Reference — một tham chiếu giữ đối tượng trong bộ nhớ bằng cách tăng retain count lên 1.
  • ARC tự động chèn các thao tác retain và release, loại bỏ quản lý bộ nhớ thủ công trong Swift và Objective-C.
  • Retain cycle xảy ra khi hai đối tượng tham chiếu lẫn nhau qua tham chiếu mạnh — bộ nhớ không bao giờ được giải phóng.
  • Weak Reference không làm tăng bộ đếm tham chiếu và tự động trở thành nil khi đối tượng được giải phóng.
  • Unowned Reference không làm tăng bộ đếm nhưng giả định rằng đối tượng không sống lâu hơn chủ sở hữu của nó.

Strong Reference là gì?

Strong Reference là một loại tham chiếu đến đối tượng ngăn chặn việc hủy nó bởi bộ thu gom rác hoặc hệ thống quản lý bộ nhớ. Miễn là có ít nhất một tham chiếu mạnh đến đối tượng, bộ nhớ của nó sẽ không được giải phóng. Đây là cơ chế cơ bản làm nền tảng cho ARC trong Swift và Objective-C cũng như bộ thu gom rác trong Java và Kotlin.

Khái niệm tham chiếu mạnh là nền tảng cho tất cả các ngôn ngữ có quản lý bộ nhớ tự động. Trong các hệ thống có ARC, mỗi tham chiếu mạnh làm tăng bộ đếm tham chiếu của đối tượng. Khi bộ đếm về 0, đối tượng được giải phóng ngay lập tức. Trong Java và Kotlin với bộ thu gom rác, tham chiếu mạnh đảm bảo rằng đối tượng có thể truy cập được và sẽ không bị GC thu gom.

Theo WWDC 2021, khoảng 35% rò rỉ bộ nhớ trong ứng dụng iOS liên quan đến việc sử dụng sai tham chiếu mạnh và retain cycles. Trong phát triển Android, rò rỉ qua tham chiếu mạnh ngầm trong closures và callbacks là nguyên nhân phổ biến thứ hai của các vấn đề bộ nhớ sau Context Leak.

Để làm việc hiệu quả với bộ nhớ, bạn cần hiểu sự khác biệt giữa các tham chiếu strong, weak và unowned, và chọn loại tham chiếu phù hợp dựa trên quyền sở hữu và vòng đời đối tượng.

ARC đã thay đổi việc quản lý bộ nhớ như thế nào

Trước ARC, các nhà phát triển phải gọi thủ công retain và release cho mỗi đối tượng, dẫn đến nhiều lỗi. ARC, được Apple giới thiệu vào năm 2011 với LLVM 3.0, đã tự động hóa quá trình này bằng cách phân tích đồ thị sở hữu tại thời điểm biên dịch. Trình biên dịch tự chèn các lệnh gọi retain, release và autorelease vào những nơi cần thiết.

Theo Clang Static Analyzer, việc giới thiệu ARC đã giảm 70% lỗi liên quan đến bộ nhớ trong các ứng dụng iOS. Đối với nhà phát triển, điều này có nghĩa là quản lý bộ nhớ đã trở nên an toàn hơn, nhưng đồng thời nảy sinh nhu cầu hiểu cách tham chiếu mạnh hoạt động bên trong — để tránh retain cycles.

Trong Kotlin và Java, bộ thu gom rác đóng vai trò của ARC, nhưng nguyên tắc tham chiếu mạnh vẫn giống nhau: GC Roots là các điểm đầu vào mà qua đó các đối tượng được giữ bởi tham chiếu mạnh. Miễn là một đối tượng có thể truy cập được qua một chuỗi tham chiếu mạnh từ GC Root, nó sẽ không bị thu gom.

Strong Reference hoạt động như thế nào trong ARC?

ARC (Automatic Reference Counting) hoạt động bằng cách đếm tham chiếu cho mỗi đối tượng trong vùng nhớ heap. Khi một tham chiếu mạnh mới đến một đối tượng được tạo, bộ đếm tăng lên (retain). Khi tham chiếu bị hủy hoặc ghi đè, bộ đếm giảm xuống (release). Khi bộ đếm về 0, đối tượng được xóa ngay lập tức khỏi bộ nhớ.

Hãy xem xét một ví dụ trong Swift. Khi một thể hiện của lớp được tạo, ARC cấp phát bộ nhớ và đặt retain count thành 1. Mỗi lần gán mới cho một biến khác sẽ tăng bộ đếm. Khi biến ra khỏi phạm vi, bộ đếm giảm xuống:

swift
class ProfileViewController {
    var nameLabel: String?
    var avatarImage: UIImage?

    func loadProfile() {
        // retain count = 1 cho thể hiện mới
        let user = User(name: "Ivan")
        // retain count = 2 sau khi gán nameLabel
        nameLabel = user.name
        // thoát phương thức — user ra khỏi phạm vi, retain count = 1
    }
}

Trong mã này, ARC đảm bảo rằng đối tượng User vẫn nằm trong bộ nhớ miễn là có ít nhất một tham chiếu mạnh trỏ đến nó. Khi hàm loadProfile kết thúc, biến cục bộ user bị hủy, nhưng nameLabel vẫn giữ đối tượng. Bộ nhớ sẽ chỉ được giải phóng khi nameLabel không còn tồn tại hoặc bị ghi đè.

Trong Kotlin, hành vi tương tự được cung cấp qua GC Roots. Miễn là tồn tại một chuỗi tham chiếu mạnh có thể theo dõi được từ gốc của bộ thu gom rác (ví dụ: trường tĩnh hoặc luồng đang hoạt động), đối tượng vẫn nằm trong bộ nhớ. Sự khác biệt là GC không giải phóng bộ nhớ ngay lập tức — việc này xảy ra không đồng bộ sau khi phân tích khả năng truy cập.

Khi nào việc giải phóng bộ nhớ xảy ra

Trong ARC, việc giải phóng xảy ra đồng bộ khi bộ đếm về 0. Trong Swift và Objective-C, bạn biết chính xác khi nào đối tượng sẽ bị xóa. Trong Kotlin và Java, thời điểm giải phóng không thể dự đoán trước, nhưng điều này được bù đắp bằng một sơ đồ linh hoạt hơn để phát hiện các phụ thuộc vòng ở cấp độ bộ thu gom rác.

Retain Cycles và rò rỉ bộ nhớ

Retain cycle (vòng giữ) — tình huống mà hai hoặc nhiều đối tượng có tham chiếu mạnh lẫn nhau. Kết quả là retain count của chúng không bao giờ về 0 và bộ nhớ không bao giờ được giải phóng, ngay cả sau khi các đối tượng không còn cần thiết cho ứng dụng.

Một ví dụ kinh điển: một bộ điều khiển view cha giữ một đối tượng con bằng tham chiếu mạnh, và đối tượng con lại giữ cha bằng tham chiếu mạnh. Điều này điển hình trong các tình huống với ủy quyền, closures và biểu thức lambda lồng nhau. Theo Instruments Leaks, retain cycles chiếm tới 60% tất cả các rò rỉ bộ nhớ trong các ứng dụng sử dụng ARC.

swift
class ParentViewController: UIViewController {
    var child: ChildViewController?

    func setupChild() {
        child = ChildViewController()
        // retain cycle: cha giữ con, con giữ cha qua closure
        child?.onEvent = {
            self.handleEvent()
        }
    }

    func handleEvent() {}
}

Vấn đề ở đây là closure onEvent nắm bắt self (ParentViewController) bằng tham chiếu mạnh, và ParentViewController tự nó giữ child bằng tham chiếu mạnh. Cả hai đối tượng sẽ không bao giờ được giải phóng. Giải pháp là sử dụng weak self trong closure để phá vỡ vòng.

Trong Kotlin, các vòng tương tự phát sinh khi sử dụng các lambda nắm bắt các đối tượng bên ngoài. Bộ thu gom rác của JVM có thể phát hiện các vòng này theo thời gian, nhưng chỉ khi các đối tượng không thể truy cập được từ GC Roots. Nếu vòng được gắn với một luồng đang hoạt động hoặc ngữ cảnh UI, rò rỉ sẽ tồn tại trong suốt vòng đời của ứng dụng.

Strong vs Weak vs Unowned Reference

Hiểu sự khác biệt giữa các loại tham chiếu là chìa khóa để quản lý bộ nhớ an toàn. Strong Reference làm tăng retain count. Weak Reference không làm tăng retain count và tự động trở thành nil khi đối tượng được giải phóng. Unowned Reference cũng không làm tăng retain count nhưng không được đặt lại thành 0 — truy cập nó sau khi giải phóng sẽ gây ra sự cố.

Loại tham chiếuRetain countAn toànKhi nào sử dụng
Strong+1An toàn (mặc định)Quyền sở hữu đối tượng, quan hệ cha → con
WeakKhông thay đổiTự động hủy (an toàn)Ủy quyền, callbacks, tham chiếu ngược
UnownedKhông thay đổiRủi ro sự cố khi truy cập muộnKhi đối tượng sống lâu hơn chủ sở hữu

Việc chọn loại tham chiếu được quyết định bởi mối quan hệ sở hữu. Nếu đối tượng B là một phần của A và không thể tồn tại nếu không có nó — hãy sử dụng Strong. Nếu B có thể tồn tại độc lập và tham chiếu đến A để nhận thông báo — hãy sử dụng Weak. Unowned hiếm khi được sử dụng — chỉ khi vòng đời của đối tượng con không vượt quá vòng đời của cha.

Quy tắc chọn thực tế

Apple Developer Documentation khuyên: theo mặc định, sử dụng strong cho tất cả các mối quan hệ sở hữu. Nếu bạn cần tránh retain cycle — hãy xác định tham chiếu nào nên là yếu. Thông thường đây là tham chiếu ngược trong hệ thống phân cấp (con → cha). Trong Kotlin, WeakReference từ java.lang.ref đóng vai trò tương tự, được sử dụng cho bộ nhớ đệm và các mẫu observer.

Cách khắc phục sự cố tham chiếu mạnh

Phát hiện retain cycles là bước đầu tiên. Bước thứ hai là loại bỏ chúng một cách chính xác. Công cụ chính để phá vỡ các vòng tham chiếu mạnh là thay thế một trong các tham chiếu bằng weak hoặc unowned. Trong các ngôn ngữ có bộ thu gom rác, WeakReference với kiểm tra null thủ công trước mỗi lần truy cập cũng được sử dụng thêm.

Trong Swift và Objective-C, cách khắc phục phổ biến nhất là thêm [weak self] vào closures. Điều này đảm bảo rằng closure không giữ đối tượng sau khi nó được giải phóng. Trong Kotlin, các trình bao bọc WeakReference hoặc xóa tham chiếu rõ ràng trong onDestroy được sử dụng cho các mục đích tương tự.

swift
class NetworkService {
    func fetchData(completion: @escaping (Data?) -> Void) {
        // nắm bắt qua weak self — đã loại trừ retain cycle
        URLSession.shared.dataTask(
            with: URL(string: "https://api.example.com")!
        ) { [weak self] data, response, error in
            guard let self else { return }
            completion(data)
        }.resume()
    }
}

Trong ví dụ này, [weak self] đảm bảo rằng NetworkService không bị closure giữ lại sau khi không còn cần thiết. Nếu self bị giải phóng trước khi yêu cầu hoàn thành — guard let self else { return } sẽ thoát khỏi closure mà không gọi completion.

Để chẩn đoán retain cycles, hãy sử dụng Instruments Leaks cho iOS hoặc Android Profiler + LeakCanary cho Android. Các công cụ này hiển thị đồ thị giữ chính xác và chỉ ra tham chiếu mạnh nào đang ngăn chặn việc giải phóng đối tượng. Việc tạo hồ sơ bộ nhớ thường xuyên nên là một phần của đường ống CI/CD của bất kỳ dự án di động nào.

Strong Reference trong Swift và Kotlin — so sánh

SwiftKotlin sử dụng các cơ chế quản lý bộ nhớ khác nhau về cơ bản, nhưng khái niệm tham chiếu mạnh tồn tại trong cả hai. Swift sử dụng ARC với giải phóng đồng bộ khi retain count = 0. Kotlin sử dụng GC theo dõi, dọn dẹp không đồng bộ các đối tượng không thể truy cập.

Tham sốSwift (ARC)Kotlin (JVM GC)
Cơ chếĐếm tham chiếu (retain count)Theo dõi khả năng truy cập (GC Roots)
Giải phóngĐồng bộ (khi bộ đếm về 0)Không đồng bộ (theo chu kỳ GC)
Retain cycleKhông tự động phát hiệnGC có thể phát hiện, nhưng không ngay
Tham chiếu yếuweak (tự động hủy)WeakReference (kiểm tra thủ công)

Sự khác biệt thực tế chính: trong Swift, retain cycle là một rò rỉ chắc chắn. Trong Kotlin, GC có thể phá vỡ vòng nếu các đối tượng không thể truy cập được từ gốc, nhưng vòng đời của các đối tượng bị rò rỉ vẫn không thể dự đoán trước. Do đó, trong cả hai ngôn ngữ, chiến lược tốt nhất là tránh các vòng tham chiếu mạnh ngay từ giai đoạn thiết kế.

Đối với Swift, hãy sử dụng weak trong các mẫu ủy quyền và closures. Đối với Kotlin, hãy sử dụng WeakReference hoặc các thành phần Lifecycle-aware tự động xóa tham chiếu khi chủ sở hữu bị hủy. Trong cả hai cách tiếp cận, mục tiêu là giống nhau — loại bỏ các tham chiếu mạnh ở nơi chúng tạo ra một chuỗi giữ không thể phá vỡ.

Câu hỏi thường gặp

Strong Reference khác Weak Reference như thế nào?

Strong Reference làm tăng retain count của đối tượng và ngăn chặn việc giải phóng nó miễn là tham chiếu còn tồn tại. Weak Reference không thay đổi retain count và tự động trở thành nil khi đối tượng bị xóa khỏi bộ nhớ. Tham chiếu mạnh được sử dụng cho quyền sở hữu, tham chiếu yếu cho các kết nối ngược và ủy quyền.

Retain cycle là gì và tại sao nó nguy hiểm?

Retain cycle — một khóa lẫn nhau trong đó hai đối tượng giữ nhau bằng các tham chiếu mạnh. Retain count của chúng không bao giờ về 0, bộ nhớ không được giải phóng. Điều này dẫn đến rò rỉ bộ nhớ: các đối tượng vĩnh viễn nằm trong heap, ứng dụng tiêu thụ ngày càng nhiều tài nguyên và cuối cùng sự cố với OutOfMemory.

Làm thế nào để phát hiện retain cycle trong ứng dụng iOS?

Sử dụng Instruments Leaks từ Xcode — chạy hồ sơ với mẫu Leaks, thực hiện một kịch bản trong ứng dụng và kiểm tra các chỉ báo rò rỉ. Để chẩn đoán chính xác, hãy chuyển sang tab Cycles & Roots — nó hiển thị đồ thị các tham chiếu mạnh lẫn nhau tạo thành một vòng không thể phá vỡ.

Khi nào nên sử dụng Unowned thay vì Weak?

Unowned nên được sử dụng khi vòng đời của đối tượng con được đảm bảo không vượt quá vòng đời của cha — ví dụ, khi liên kết một đối tượng với một phạm vi được xác định chặt chẽ. Nếu nghi ngờ, hãy sử dụng Weak, vì truy cập tham chiếu unowned đã giải phóng sẽ gây ra sự cố ứng dụng.

Các tham chiếu mạnh có ảnh hưởng đến hiệu suất ứng dụng không?

Gián tiếp — có. Mỗi retain và release trong ARC là một thao tác nguyên tử với chi phí. Với số lượng lớn đối tượng trong các vòng, điều này có thể ảnh hưởng đến hiệu suất. Tuy nhiên, vấn đề chính không phải là tốc độ của ARC, mà là rò rỉ bộ nhớ do chọn sai loại tham chiếu.

Tổng kết

  • Strong Reference là cơ chế cơ bản của quyền sở hữu đối tượng, giữ nó trong bộ nhớ bằng cách tăng retain count.
  • ARC tự động hóa quản lý bộ nhớ trong Swift và Objective-C, loại bỏ retain và release thủ công, nhưng không bảo vệ khỏi retain cycles.
  • Retain cycle xảy ra với các tham chiếu mạnh lẫn nhau — đây là nguyên nhân chính gây rò rỉ bộ nhớ trong các hệ thống ARC.
  • Các tham chiếu WeakUnowned phá vỡ các vòng tham chiếu mạnh mà không làm tăng retain count.
  • Việc chọn loại tham chiếu được xác định bởi mối quan hệ sở hữu: Strong cho cha→con, Weak hoặc Unowned cho con→cha.
  • Instruments LeaksLeakCanary là các công cụ chính để phát hiện các tham chiếu mạnh có vấn đề trong iOS và Android.
  • Thiết kế đồ thị sở hữu trước — rẻ hơn là sửa rò rỉ bộ nhớ sau khi phát hành ứng dụng.

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