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) 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.
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.
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 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ểm | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| Thời điểm giải phóng | Xác định: ngay khi bộ đếm về không | Không xác định: ở chu kỳ thu gom tiếp theo |
| Tạm dừng thực thi | Khô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ếu | Duyệ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ổ sung | Không yêu cầu | Yê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.
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 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 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 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.
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.
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ộ.
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.
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.
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 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
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 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.
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 (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.
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
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.
Đọc thêm