ARC in Swift

by Hoang Anh Tuan
690 views

Việc quản lí bộ nhớ trong mỗi ứng dụng là rất quan trọng. Nếu ứng dụng của bạn sử dụng dữ liệu bừa bãi 1 cách không cần thiết thì sẽ làm memory của ứng dụng tăng cao, và trong trường hợp xấu có thể dẫn đến crash.
Swift sử dụng ARC để hỗ trợ developer quản lí ứng dụng của app. Ở bài viết này mình sẽ nói về ARC trong swift.

Contents:

  • ARC là gì
  • Cách ARC hoạt động
  • Strong reference
  • Xử lí strong reference
  • weak reference

ARC là gì

  • ARC là viết tắt của Automatic Reference Counting. Swift sử dụng ARC để giúp bạn việc quản lý bộ nhớ của app.
  • ARC sẽ tự động giải phóng bộ nhớ được sử dụng bởi 1 biến khi biến đó không còn được sử dung nữa.
  • Tuy nhiên, để ARC có thể biết được khi nào biến đó không còn dc sử dụng, thì ARC phải biết về các relations giữa biến đó vs các biến khác trong code của bạn.

Note: ARC chỉ áp dụng cho các biến kiểu class(reference type).

Cách ARC hoạt động

  • Mỗi khi bạn tạo 1 biến, ARC sẽ tự chia sẻ 1 phần bộ nhớ để lưu trữ biến đó. Khi biến đó không còn được sử dụng, ARC giải phóng biến, lấy lại bộ nhớ để dùng cho những mục đích khác
  • Để xác định 1 biến không còn được sử dụng, ARC sẽ đếm số lượng strong reference hiện tại của biến đó đối với các biến khác. Chỉ khi số lượng strong reference = 0 thì biến đó mới được giải phóng bộ nhớ.

Vậy strong reference là gì?

Strong reference

Mỗi khi bạn gán 1 biến kiểu class cho 1 property, constant hoặc variable, thì những thứ đó tạo 1 strong reference đến biến đó.

class Car {
    var price: Double
    init(price: Double) {
        self.price = price
    }
    
    deinit {
        print("Car is being deinitialized")
    }
}

var hondaCar: Car? = Car(price: 50000.0)

Ở đây, bạn gán biến Car(price: 50000.0) cho varialbe hondaCar
-> hondaCar đã tạo 1 strong reference đến Car(price: 50000.0)

Thêm dòng code sau và run thử trên playground:

hondaCar = nil

Kết quả thu được ở màn hình console như sau:

Nếu bạn set honedaCar = nil -> Khi đó Car(price:50000) sẽ không còn strong reference nào trỏ đến -> ARC sẽ giải phóng bộ nhớ cho Car.
Tiếp tục thử chạy đoạn code sau và quan sát màn hình console:

Chạy đoạn code trên, bạn sẽ không thấy được print ra "Car is being deinitialized"…
Vì các biến car1, car2, car3 là reference type, nên ta có sơ đồ relation ship như sau:

Khi đó nếu chỉ set car1, car2 = nil, thì Car vẫn còn 1 strong reference từ car3 trỏ đến -> ARC sẽ không giải phóng bộ nhớ cho Car trong trường hợp này.

Strong reference giữa các class

Trong ví dụ trên, sẽ tạo ra 1 sơ đồ relation giữa các instance như sau:

  • Việc set property departmentowner của 2 instance tuangotham đã tạo ra 2 strong reference trỏ đến nhau.
  • Khi đó Person và Departmen, mỗi thứ sẽ có 2 strong refernce trỏ đến. Vì vậy, khi bạn chỉ set tuan = nilgotham = nil thì Person và Departmen vẫn sẽ không được xóa khỏi bộ nhớ dù không cần dùng đến nữa.
    -> Trường hợp này gọi là retain cycle -> Tạo ra rò rỉ bộ nhớ (memory leaks).

Xử lí strong reference

Swift cung cấp 2 cách để xử lí strong reference cycle khi bạn làm việc với các biến kiểu class:

  • weak reference
  • unowned reference

Weak reference

  • weak refernce: thay vì tạo 1 strong reference, nó sẽ chỉ tạo 1 weak -> ARC sẽ không làm tăng số lượng strong reference trỏ tới biến đó.
  • Sử dụng weak reference khi biến còn lại có thời gian tồn tại ngắn hơn(thường bị giải phóng bộ nhớ trước)
class Person {
    weak var department: Department?
    
    deinit {
        print("Person is being deinitialized")
    }
}

Sửa lại ví dụ trên bằng cách sửa department thành kiểu weak( vì department có thời gian tồn tại ngắn hơn người).
Khi đó, sơ đồ relation ship sẽ thành:

  • set gotham = nil -> Departmen k còn strong reference nào trò đến -> được giải phóng khỏi bộ nhớ, khi đó strong reference của Departmen trò đến Person cũng sẽ bị mất.
  • set tuan = nil -> Person không còn strong refernce nào trỏ đến -> ARC giải phóng khỏi bộ nhớ.
    Kết quả thu được:

Unowned reference:

  • Tương tự như weak refernce, nó sẽ không tạo ra 1 strong reference.
  • Khác weak reference ở chỗ, unowned dùng cho những biến có thời gian tồn tại lâu hơn.

Note: nếu bạn truy cập đến 1 biến unowned sau khi nó đã được giải phóng khỏi bộ nhớ -> app sẽ crash. Vì vậy hãy cẩn thận khi dùng unowned.
Sửa ví dụ trên thành như sau và sẽ vẫn thu được kết quả tương tự so với khi dùng weak:

class Department {
    unowned var owner: Person?
    
    deinit {
        print("Department is being deinitialized")
    }
}

Hi vọng bài viết trên đã đem lại cho bạn cái nhìn tổng quan về ARC và cách sử dụng weak/unowned để tránh retain cycle giữa các class.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.