ARC and Memory Management in Swift Part 2

Tiếp theo từ phần trước

Link tutorial gốc

Weak References

Để phá vỡ strong reference cycles, bạn có thể xác định rõ mối quan hệ reference giữa 2 objects là weak. Trừ khi có quy định khác thì tất cả các references đều là strong. Weak references thì ngược lại, không increase strong reference count của 1 object. Nói cách khác, weak reference không tham dự vào việc quản lý lifetime của object. Thêm vào đó, weak reference luôn luôn được khai báo là optional types. Tức là khi reference count bằng 0, reference đó sẽ được set về nil.

WeakReference-480x206.png

Mũi tên nét đứt ở hình trên thể hiện weak reference. Chúng ta có thể thấy rằng: reference count của object1 là 1 vì chỉ có variable1 tham chiếu đến nó; reference count của object2 là 2 bởi vì cả variable2 và object1 đều tham chiếu đến nó. Khi object2 tham chiếu ngược lại object1, nó là 1 weak reference và ko làm tăng reference count của object1.

Khi cả variable1 và variable2 bị deallocated, object1 sẽ biến mất vì reference count = 0, đồng thời khi đó strong reference từ object1 đến object2 cũng biến luôn theo => reference count của object2 là 0 => object2 được giải phóng. Quay trở lại playground, phá vỡ reference cycle của User-Phone bằng cách dùng weak reference như sau:

class Phone {
  weak var owner: User?
  // other code...
}

UserIphoneCycleWeaked-480x202.png

Unowned References

Có 1 kiểu reference nữa mà ko làm tăng reference count: unowned. Vậy thì điều khác nhau giữa unownedweak là gì? Một weak reference luôn luôn là kiểu optional và sẽ tự động trở thành nil khi referenced object bị deinitializes. Đó là lý do bạn luôn phải define weak properties là optional var types. Unowned references, ngược lại, không bao giờ là optional types. Nếu bạn cố gắng access một unowned property mà refers tới 1 deinitialized object, bạn sẽ kích hoạt 1 lỗi runtime eror comparable to force unwrapping a nil optional type. Reference Types Để thực hành với unowned, thêm 1 class CarierSubcription trước doblock như sau:

class CarrierSubscription {
  let name: String
  let countryCode: String
  let number: String
  let user: User
 
  init(name: String, countryCode: String, number: String, user: User) {
    self.name = name
    self.countryCode = countryCode
    self.number = number
    self.user = user
 
    print("CarrierSubscription \(name) is initialized")
  }
 
  deinit {
    print("CarrierSubscription \(name) is being deallocated")
  }
}

CarrierSubscription có 4 properties: name, countryCode, number, và 1 reference tới 1 đối tượng User. Tiếp theo add thêm 1 mảng các CarrierSubscription objects vào class User:

var subscriptions: [CarrierSubscription] = []

Đồng thời, add đoạn code sau vào class Phone, ngay sau owner property:

var carrierSubscription: CarrierSubscription?
 
func provision(carrierSubscription: CarrierSubscription) {
  self.carrierSubscription = carrierSubscription
}
 
func decommission() {
  self.carrierSubscription = nil
}

Chúng ta vừa add 1 optionnal property kiểu CarrierSubscription và 2 functions để provision hoặc decommission một nhà cung cấp thuê bao trên điện thoại. Tiếp theo, add đoạn code sau vào hàm init của CarrierSubscription ngay trước câu lệnh print:

user.subscriptions.append(self)

để chắc chắn rằng CarrierSubscription đó sẽ đc add vào array các subscriptions của user. Cuối cùng, trong do block, thêm đoạn code để trở thành:

do { 
  let user1 = User(name: "John")
  let iPhone = Phone(model: "iPhone 6s Plus")
  user1.add(phone: iPhone)
  let subscription1 = CarrierSubscription(name: "TelBel", countryCode: "0032", number: "31415926", user: user1)
  iPhone.provision(carrierSubscription: subscription1)
}

Bạn sẽ lại thấy rằng "John" "iPhone" "TelBel" được init nhưng ko hề đc deinit, tức là lại có vấn đề về reference cycle ở đây: cả user1, iPhone, và subscription1 đều không đc deallocated: Mỗi reference từ user1 tới subscription1 hoặc từ subscriotion1 tới user1 nên là unowned để phá vỡ reference cycle, nhưng vấn đề là chọn reference nào để đặt là unowned? Rất đơn giản: user sở hữu một thuê bao (carrier subscription), carrier subscription không thể sở hữu 1 user. Hơn nữa, 1 CarrierSubscription sẽ không thể tồn tại mà ko có 1 user sở hữu nó. Vì lý do đó mà bạn khai báo property user là let ở trong class CarrierSubscription. Bởi vì user sở hữu CarrierSubscription nên, dĩ nhiên property user bên trong CarrierSubscription sẽ là unowned:

class CarrierSubscription {
  let name: String
  let countryCode: String
  let number: String
  unowned let user: User
  // Other code...
}

Và nó sẽ phá vỡ reference cycle: Tuy nhiên ta sẽ có câu hỏi được đặt ra là: tại sao phải sử dụng unowned? hoặc, khi nào thì sử dụng unowned? Tại sao không qui về hết var + weak cho đơn giản? Câu trả lời cho câu hỏi này đã có ngay ở trên: đó là khi ta cần phải sử dụng những property có reference chéo nhưng lại bắt buộc phải có (tức là sử dụng let) - như trường hợp property User bên trong CarrierSubscription!