ARC: nó là gì, nguyên lý hoạt động của Automatic Reference Counting trong iOS

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

Automatic Reference Counting (ARC) là hệ thống quản lý bộ nhớ trong Swift và Objective-C, tự động đếm số lượng tham chiếu đến mỗi đối tượng và giải phóng nó khi bộ đếm về không. Theo Tài liệu Apple Swift, 2026, ARC được nhúng vào trình biên dịch và hoạt động ở giai đoạn biên dịch, chèn các lệnh gọi retain/release vào đúng vị trí. Không giống như Garbage Collection, ARC không yêu cầu luồng thu gom riêng và không tạo ra sự tạm dừng trong quá trình thực thi ứng dụng.

Những điểm chính

  • ARC — Automatic Reference Counting, hệ thống quản lý bộ nhớ dựa trên trình biên dịch trong Swift và Objective-C
  • Nguyên lý hoạt động — mỗi đối tượng có bộ đếm tham chiếu (retain count), khi về không đối tượng được giải phóng ngay lập tức
  • Bộ định tính — strong, weak và unowned xác định cách tham chiếu ảnh hưởng đến bộ đếm và vòng đời của đối tượng
  • Khác biệt với GC — ARC hoạt động xác định ở giai đoạn biên dịch, không có tạm dừng Stop-The-World hay luồng thu gom nền
  • Retain Cycle — vấn đề chính của ARC: nếu hai đối tượng tham chiếu lẫn nhau qua strong, bộ đếm của chúng không bao giờ về không

ARC là gì?

ARC (Automatic Reference Counting) là cơ chế quản lý bộ nhớ dựa trên trình biên dịch được Apple giới thiệu trong Xcode 4.2 (2011) cho Objective-C và được Swift kế thừa. Không giống như quản lý bộ nhớ thủ công (Manual Retain-Release, MRR), ARC tự động hóa hoàn toàn các lệnh gọi retain, release và autorelease, chèn chúng ở giai đoạn biên dịch mà không cần sự can thiệp của nhà phát triển.

ARC không phải là bộ thu gom rác. Đó là phân tích tĩnh với chèn mã động: trình biên dịch phân tích vòng đời của đối tượng và đặt retain/release tại các điểm nơi đối tượng được tạo, sao chép hoặc ra khỏi phạm vi. Kết quả là giải phóng bộ nhớ xác định: đối tượng bị xóa ngay khi không còn tham chiếu nào trỏ đến nó, không có độ trễ hay tạm dừng.

Theo WWDC 2011 Session 323, việc chuyển từ MRR sang ARC đã giảm 70% lỗi crash liên quan đến bộ nhớ trong các ứng dụng Apple. Các nhà phát triển không còn phải cân bằng thủ công retain/release, loại bỏ toàn bộ lớp rò rỉ và lỗi double-free.

Cách Automatic Reference Counting hoạt động

Mỗi đối tượng trong bộ nhớ có một bộ đếm tham chiếu (retain count). Khi một đối tượng được tạo, bộ đếm được đặt thành 1. Khi một tham chiếu strong mới trỏ đến đối tượng — bộ đếm tăng (retain). Khi một tham chiếu strong biến mất — bộ đếm giảm (release). Khi về không, đối tượng được giải phóng ngay lập tức.

Trình biên dịch Swift không chèn retain/release ở mọi phép gán — nó sử dụng phân tích tĩnh để tối ưu hóa. Ví dụ, nếu một đối tượng được đảm bảo không được sử dụng sau khi được truyền đi, trình biên dịch có thể bỏ qua release/retain không cần thiết. Tối ưu hóa này được gọi là ARC Optimization.

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) initialized (retain count: 1)")
    }
    deinit {
        print("\(name) deallocated")
    }
}

func testARC() {
    let p = Person(name: "Alice")  // retain count = 1
    let q = p                      // retain count = 2
    // q ra khỏi phạm vi
    // retain count = 1
    // p ra khỏi phạm vi
    // retain count = 0 → deinit
}

Ví dụ này cho thấy ARC quản lý bộ đếm như thế nào: khi gán q = p, bộ đếm tăng; khi q ra khỏi phạm vi, bộ đếm giảm. Khi tham chiếu strong cuối cùng biến mất, bộ hủy được gọi ngay lập tức. Không có bộ thu gom rác nào chờ đợi — bộ nhớ được giải phóng ngay lập tức.

ARC vs Garbage Collection: khác biệt chính

ARC và Garbage Collection giải quyết cùng một vấn đề — quản lý bộ nhớ tự động — nhưng với cách tiếp cận khác nhau về cơ bản. Sự lựa chọn giữa chúng xác định kiến trúc ngôn ngữ: Swift (ARC) vs Java/Go (GC). Hãy xem những khác biệt chính.

Đặc điểmARC (Swift/ObjC)GC (Java/Go)
Thời điểm giải phóngXác định: ngay khi bộ đếm về khôngKhông xác định: ở chu kỳ thu gom tiếp theo
Tạm dừng thực thiKhông có (retain/release chèn ở biên dịch)Tạm dừng Stop-The-World (2–200 ms)
Chi phíTăng/giảm bộ đếm ở mỗi tham chiếuDuyệt đồ thị đối tượng, đánh dấu, quét dọn
Vấn đềRetain Cycle (giải quyết thủ công)Phân mảnh heap, rò rỉ từ tham chiếu bị quên
Luồng bổ sungKhông yêu cầuYêu cầu luồng thu gom rác

Sự đánh đổi chính: ARC cung cấp vòng đời đối tượng có thể dự đoán và không tạm dừng, nhưng yêu cầu nhà phát triển hiểu về retain cycle và chọn weak/unowned chính xác. GC giải phóng nhà phát triển khỏi những mối quan tâm này, nhưng với cái giá là tạm dừng không xác định và một luồng bổ sung.

Strong, Weak và Unowned: bộ định tính tham chiếu trong ARC

ARC định nghĩa ba loại bộ định tính tham chiếu, mỗi loại ảnh hưởng đến bộ đếm và vòng đời đối tượng khác nhau. Chọn đúng bộ định tính là nền tảng của quản lý bộ nhớ an toàn trong Swift.

Strong

Strong là bộ định tính mặc định. Mỗi tham chiếu strong tăng retain count của đối tượng lên 1. Miễn là còn ít nhất một tham chiếu strong, đối tượng vẫn sống. Tất cả thuộc tính lớp và biến cục bộ trong Swift đều là strong theo mặc định. Tham chiếu strong tạo ra mối quan hệ sở hữu: đối tượng A sở hữu đối tượng B.

Weak

Weak là tham chiếu không làm tăng retain count. Một đối tượng có thể được giải phóng ngay cả khi có tham chiếu weak trỏ đến nó. Sau khi giải phóng, tham chiếu weak tự động được đặt thành nil. Tham chiếu weak luôn được khai báo là var với kiểu tùy chọn (?). Chúng được sử dụng để phá vỡ retain cycle, đặc biệt trong mẫu delegate.

Unowned

Unowned là tham chiếu không sở hữu, giống như weak, không làm tăng retain count. Tuy nhiên, tham chiếu unowned không được đặt thành nil sau khi giải phóng — truy cập vào đối tượng đã giải phóng gây ra crash. Unowned được sử dụng khi đảm bảo rằng đối tượng sống ít nhất bằng đối tượng tham chiếu. Các trường hợp điển hình là closures và mối quan hệ cha-con với vòng đời được đảm bảo.

swift
class Customer {
    let name: String
    var card: CreditCard?         // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

class CreditCard {
    let number: String
    unowned let customer: Customer   // unowned — không sở hữu
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deallocated") }
}

var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer và CreditCard đều được giải phóng — không có retain cycle

Ở đây CreditCard sử dụng tham chiếu unowned đến Customer. Customer sở hữu thẻ (strong), và thẻ không sở hữu khách hàng (unowned). Khi Customer được giải phóng, cả hai đối tượng đều được giải phóng — không có retain cycle xảy ra. Nếu card.customer là strong, vòng lặp sẽ chặn việc giải phóng.

Vấn đề thường gặp của ARC và cách giải quyết

Mặc dù tự động hóa, ARC không phải là giải pháp vạn năng. Các nhà phát triển gặp phải một số vấn đề điển hình đòi hỏi hiểu biết về cơ chế quản lý bộ nhớ nội bộ.

Retain Cycle trong Closures

Closures trong Swift capture các biến bên ngoài bằng tham chiếu strong. Nếu một closure được gán cho một thuộc tính lớp và capture self — một retain cycle xảy ra: lớp giữ closure, closure giữ self. Giải pháp là danh sách capture với weak hoặc unowned.

swift
class NetworkManager {
    var completionHandler: ((Data?) -> Void)?
    var data: Data?

    func fetchData() {
        completionHandler = { [weak self] result in
            guard let self else { return }
            self.data = result
            self.processResult()
        }
    }

    func processResult() { }
}

Danh sách capture [weak self] tạo tham chiếu weak đến self bên trong closure. Điều này phá vỡ retain cycle tiềm ẩn. Guard let self đảm bảo đối tượng còn sống trước khi thực thi mã. weak self là thực hành tiêu chuẩn cho closures bất đồng bộ trong Swift.

Hiệu suất retain/release

Mặc dù retain/release là các thao tác nhẹ, việc tăng/giảm bộ đếm thường xuyên trong các vòng lặp nóng tạo thêm chi phí. Trong Swift 5.9+, trình biên dịch sử dụng tối ưu hóa loại bỏ retain/release dư thừa nếu bộ phân tích chứng minh được an toàn. Tuy nhiên, trong Objective-C, retain/release vẫn có thể là nút thắt cổ chai trong các kịch bản tải cao với hàng triệu lệnh gọi mỗi giây.

Autorelease Pool

Autorelease Pool là cơ chế giải phóng trì hoãn được sử dụng trong Objective-C và một số kịch bản Swift. Các đối tượng được đặt vào pool và nhận release khi pool được dọn. Trong các vòng lặp có nhiều đối tượng tạm thời (ví dụ: phân tích JSON), tạo autoreleasepool tùy chỉnh giúp giảm mức tiêu thụ bộ nhớ đỉnh.

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

ARC khác với quản lý bộ nhớ thủ công (MRR) như thế nào?

Trong quản lý thủ công (MRR), nhà phát triển gọi rõ ràng retain, release và autorelease. ARC tự động chèn các lệnh gọi này ở giai đoạn biên dịch, loại bỏ rủi ro double-free, rò rỉ do quên release và lỗi cân bằng retain/release.

ARC có thể hoạt động với mã C/C++ không?

ARC chỉ quản lý các đối tượng Objective-C và lớp Swift. Đối với cấu trúc và con trỏ C/C++, ARC không áp dụng — các đối tượng này được quản lý thủ công hoặc qua con trỏ thông minh C++ (shared_ptr, unique_ptr). Các đối tượng Core Foundation (CFString, CGColor) cũng không thuộc phạm vi ARC.

Khi nào sử dụng weak và khi nào sử dụng unowned?

weak — khi đối tượng có thể được giải phóng trước đối tượng tham chiếu (delegate, closures bất đồng bộ). unowned — khi đảm bảo rằng đối tượng sống ít nhất bằng đối tượng tham chiếu (cha-con nơi con không thể tồn tại nếu không có cha). Nếu không chắc chắn, hãy chọn weak.

Kiểu tồn tại (Existential Type) là gì và chúng ảnh hưởng đến ARC như thế nào?

Kiểu tồn tại (protocol như kiểu) trong Swift bọc giá trị trong một container đặc biệt (existential container). Điều này làm tăng số lượng retain/release tại ranh giới giao thức. Trong Swift 5.7+, kiểu kết quả không minh bạch (opaque result type) và tham số some giảm chi phí bằng cách loại bỏ container.

Làm thế nào để kiểm tra retain count trong Swift?

Không có API trực tiếp để đọc retain count trong Swift — nó được coi là chi tiết triển khai. Để chẩn đoán, sử dụng Instruments (Allocations, Leaks) hoặc Trình gỡ lỗi Bộ nhớ trong Xcode. Các công cụ này hiển thị số lượng phiên bản lớp đang sống và chuỗi lưu giữ.

Tổng kết

  • ARC — hệ thống quản lý bộ nhớ dựa trên trình biên dịch cho Swift và Objective-C hoạt động qua đếm tham chiếu
  • Nguyên lý — mỗi đối tượng có retain count; khi về không, đối tượng được giải phóng ngay lập tức và xác định
  • Khác biệt với GC — ARC hoạt động không cần luồng nền hay tạm dừng Stop-The-World, nhưng yêu cầu kiểm soát retain cycle
  • Strong — tăng bộ đếm; weak và unowned không tăng, nhưng unowned không được đặt nil khi giải phóng
  • Closures — nguyên nhân chính của retain cycle trong Swift; danh sách capture [weak self] là giải pháp tiêu chuẩn
  • Autorelease Pool — cơ chế giải phóng trì hoãn cho các đối tượng tạm thời trong vòng lặp và kịch bản tùy chỉnh
  • Chẩn đoán — Xcode Memory Debugger, Instruments và LeakCanary (qua cầu ObjC) để tìm vấn đề

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