ARC and Memory Management in Swift Part 3
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
Reference Cycles with Closures
Reference cycles của các objects xuất hiện khi các property reference lẫn nhau. Giống như object, closures cũng là reference types nên cũng có thể xảy ra cycles. Closures giữ lại đối tượng chỗ nó khai báo để thực hiện khối lệnh ở trong (Closures capture (or close over) the objects that they operate on.) ví dụ: nếu 1 closure được assigned như 1 property của 1 class, và closure sử dụng instance property của class đó, bạn sẽ có 1 reference cycle. Nói cách khác, object giữ 1 reference tới closure thông qua 1 stored property, closure lại giữ 1 reference tới object thông qua captured value of self
:
Thêm đoạn code sau vào CarrierSubscription
, ngay sau property user
:
lazy var completePhoneNumber: () -> String = {
self.countryCode + " " + self.number
}
Closure này tính toán và trả về 1 số điện thoại đầy đủ (gồm số điện thoại và mã quốc gia). Closure này được khai báo là lazy
nghĩa là nó sẽ không bị assigned cho đến khi sử dụng lần đầu. Bắt buộc phải dùng lazy
bởi vì bên trong closure, ta sử dụng self.countryCode
và self.number
, mà những thứ này không thể available trừ khi hàm init chạy xong. Thêm dòng sau vào bên trong do
block:
print(subscription1.completePhoneNumber())
Bạn có thể thấy ngay ở dưới: user1
và iPhone
được deallocate nhưng CarrierSubscription
thì không, do strong reference cycle giữa object và closure.
Swift có 1 cách vô cùng thanh lịch và đơn giản để phá vỡ strong reference cycles trong closures. Bạn khai báo 1 capture list
trong đó bạn định nghĩa rõ mỗi quan hệ giữa closure và objects mà nó captures. Để minh hoạ rõ ràng capture list
hoạt động, bạn hãy xem xét đoạn code sau đây:
var x = 5
var y = 5
let someClosure = { [x] in
print("\(x), \(y)")
}
x = 6
y = 6
someClosure() // Prints 5,6
print("\(x), \(y)") // Prints 6,6
Biến x được đặt trong capture list
, nên 1 copy của x
được tạo ra tại thời điểm closure được defined. Tức là x
được capture bởi value, ngược lại, y
không có trong capture list
nên nó được capture bởi reference. Có nghĩa là khi closure runs, y
sẽ có giá trị ở thời điểm đó chứ ko phải thời điểm bị capture. Capture list
rất hữu ích cho việc xác định 1 weak relationship
hoặc unowned relationship
giưã objects và closure. Trong trường hợp này, unowned
là rất phù hợp bởi closure
không tồn tại khi mà instance của CarrierSubscription
đã bị giải phóng. Thay đổi completePhoneNumber
closure trong CarrierSubscription
như sau:
lazy var completePhoneNumber: () -> String = {
[unowned self] in
self.countryCode + " " + self.number
}
Ta đã add [unowned self]
vào capture list của closure: tức là self
bị captured như unowned` reference chứ không phải strong reference.
Syntax chúng ta dùng ở trên thực chất là viết tắt của 1 form dài hơn như sau:
var closure = {
[unowned newID = self] in
// Use unowned newID here...
}
Ở đây, newID
là 1 unowned
copy của self
. Bên ngoài scope của closure, self
vẫn giữ ý nghĩa nguyên của nó. Khi viết tắt, 1 biến self
mới được tạo ra mà thực chất là shadows
của biến self
đang tồn tại, và chỉ dùng trong closure's scope.
Trong code của bạn, mối quan hệ giữa self
và completePhoneNumber
closure là unowned
. Nếu bạn chắc chắn rằng 1 reference object từ closure không bao giờ deallocate, bạn có thể dùng unowned
. Nếu nó deallocate, bạn sẽ gặp rắc rối =)). Thêm đoạn code sau vào cuối playground:
class WWDCGreeting {
let who: String
init(who: String) {
self.who = who
}
lazy var greetingMaker: () -> String = {
[unowned self] in
return "Hello \(self.who)."
}
}
let greetingMaker: () -> String
do {
let mermaid = WWDCGreeting(who: "Caffinated Mermaid")
greetingMaker = mermaid.greetingMaker
}
greetingMaker() // TRAP!
Playground sẽ báo 1 runtime exception bởi vì closure hy vọng rằng self.who vẫn còn valid, nhưng nó đã bị deallocated bởi mermaid
đã out of scope. Ví dụ này có vẻ giả tạo và đần độn =)) nhưng điều này rất dễ xảy ra trong thực tế ví dụ như: khi bạn sử dụng closure để chạy 1 cái gì đó sau này, như sau khi 1 asynchronous network call đã hoàn tất. Thay đổi greetingMaker
trong WWDGreeting
như sau:
lazy var greetingMaker: () -> String = {
[weak self] in
return "Hello \(self?.who)."
}
Có 2 điều thay đổi: thay unowned
bằng weak
và bởi vì self
là weak
nên bạn sẽ phải access vào property who
với self?.who
Playground sẽ không bị crashes, nhưng bạn sẽ nhận được kết quả bên sidebar: "Hello nil."
. Có lẽ xử lý ntn là chấp nhận được, nhưng nếu bạn thường muốn làm gì đó hoàn toàn khác khi mà object đã bị giải phóng, thì Swift's guard let
sẽ rất dễ dàng.
Viết lại closure lần cuối cùng như sau:
lazy var greetingMaker: () -> String = {
[weak self] in
guard let strongSelf = self else {
return "No greeting available."
}
return "Hello \(strongSelf.who)."
}
guard
statement binds a new variable strongSelf
từ weak self
. Nếu self
là nil
, closure sẽ trả về "No greeting available"
. Ngược lại nếu self
không nil
, strongSelf
sẽ tạo ra 1 strong reference, để object vẫn có thể "sống" đến khi closure kết thúc.
Cách làm này thường được gọi là strong-weak dance
, vô cùng quen thuộc từ hồi objective-C.
All rights reserved