ARC and Memory Management in Swift Part 2
Bài đăng này đã không được cập nhật trong 3 năm
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.
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...
}
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 unowned
và weak
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.
Để thực hành với unowned
, thêm 1 class CarierSubcription trước do
block 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
!
All rights reserved