Memory Management
Swift 跟 Objective-C 一樣,採用 Automatic Reference Counting (ARC)。但是除了 weak
, strong
之外,還多了一些東西。
Automatic Reference Counting (ARC)
先舉個 ARC 的概念:
class BowlingPin {}
func juggle(count: Int) {
var left = BowlingPin()
以上的程式碼,代表,left
這個變數(variable) retain(可以翻譯成保有) 了 BowlingPin()
這個 instance。用圖片來表達就像這樣:
接著我們在新增一個變數(variable):
class BowlingPin {}
func juggle(count: Int) {
var left = BowlingPin()
if count > 1 {
var right = BowlingPin()
就會變成下圖這樣,left
跟 right
分別 retain 一個 instance:
接著我們執行 right = left
:
class BowlingPin {}
func juggle(count: Int) {
var left = BowlingPin()
if count > 1 {
var right = BowlingPin()
right = left
left
跟 right
就會 retain 同一個物件了,而原本 right
retain 的 BowlingPin
instance 就消失了,只剩下一個 BowlingPin
instance:
接著,當這個 function 結束時,全部的 BowlingPin
instance 就會消失:
class BowlingPin {}
func juggle(count: Int) {
var left = BowlingPin()
if count > 1 {
var right = BowlingPin()
right = left
} // right 生命週期結束
} // left 生命週期結束
這就是 ARC 的基本觀念。
Ownership
假設我們有一個 Apartment
的 class,Apartment
裡面有可能會住人,用 Person
class 表示。而 Person
裡也要知道這個 person 住的是哪間 Apartment
。
接著有一個 method moveIn(apt: Apartment)
,表示這個 Person
要搬進哪一間 Apartment
:
class Apartment {
var tenant: Person?
}
class Person {
var home: Apartment?
func moveIn(apt: Apartment) {
self.home = apt
apt.tenant = self
}
}
接著就建立 Apartment
及 Person
的 instance,renters
與 apts
:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
用圖片表示:
接下來,假設有 Elsvette
搬進 507
:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
renters["Elsvette"]!.moveIn(apts[507]!)
用圖片表示:
此時,如果把 renters["Elsvette"]
設為 nil:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
renters["Elsvette"]!.moveIn(apts[507]!)
renters["Elsvette"] = nil
會發現,507: Apartment()
沒有消失,因為他被 Persion
中的 home
retain 住了:
此時,再把 apts[507]
設為 nil
:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
renters["Elsvette"]!.moveIn(apts[507]!)
renters["Elsvette"] = nil
apts[507] = nil
就會發現,發生 memory leak 了,因為 home
跟 tenant
互相 retain,所以無法被釋放:
Weak References
為了避免這個問題,Swift 提供了 weak reference,keyword 為 weak
,weak
代表一個「弱」連結,它表示說:「當沒有『強』的連結連到我時,我就自動釋放這個 instance」。
因此上述的程式可以改成以下這樣:
class Apartment {
weak var tenant: Person?
}
class Person {
weak var home: Apartment?
func moveIn(apt: Apartment) {
self.home = apt
apt.tenant = self
}
}
所以重新檢視一遍剛剛的程式碼:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
renters["Elsvette"]!.moveIn(apts[507]!)
用圖片表示,虛線就代表 weak
的 retain:
因此,當我們把 renters["Elsvette"]
設為 nil
:
var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]
renters["Elsvette"]!.moveIn(apts[507]!)
renters["Elsvette"] = nil
renters["Elsvette"]
就會自動被釋放,而 Apartment
中的 tenant: Person?
也會因為沒有「強」連結連著它而自動釋放:
Using Weak Reference
在 Swift 與 Objective-C 中的 weak reference ,跟一個很大的差別,那就是:「Weak Reference 在 Swift 為 optional value」。也就是說,你可以用 option value 的語法去操作 weak reference 的變數,例如:
if let tenant = apt.tenant {
tenant.buzzIn()
}
以上的方法是 Optional Binding,也就是說 tenant
是一個 strong reference,因此可以直接存取 tenant 的值。
另外當然也可以用 Optional Chaining,在 apt
後面加上 ?
:
apt.tenant?.buzzIn()
另外,要注意的是,當你在「測試」weak reference 時,並不會產生 strong reference:
Testing a weak reference alone does not produce a strong reference.
例如有一個 method 為 cashRentCheck()
,這個 method 會釋放(dealloc) tenant
,讓 tenant
變成 nil
。因此以下的操作就會出現 runtime error:
if apt.tenant {
apt.tenant!.cashRentCheck()
apt.tenant!.greet() // tenant is nil, error!
}
instance 之間的 method 呼叫時,如果用 chaining,也不會保留 strong reference:
Chaining doesn't preserve a strong reference between method invocations.
apt.tenant?.cashRentCheck()
apt.tenant?.greet() // tenant is nil, error!
Same-Lifetime Relationships
假設我們要建立信用卡與持卡人的關係。
Person
代表持卡人,CreditCard
代表信用卡,而一張信用卡只能有一個持有人,而且不能是 nil
(有卡片一定會有持卡人),因此我們設計如下:
class Person {
var card: CreditCard?
}
class CreditCard {
let holder: Person
init(holder: Person) {
self.holder = holder
}
}
但是問題來了,我們會發現,這讓 Persion
與 CreditCard
產生了 retain cycle,彼此互相 retain,因此會造成 memory leak。
那我們把 let holder: Person
改成 weak,就會變成 weak var holder: Person?
,這也不行呀,因為 holde
不能為 nil
,所以不能是 optional 的。我們發現,在這個「信用卡」與「持卡人」的情況下,card
跟 holder
會是同時吋在,且同時消失的。也就是說,它們的 生命週期 是一樣的。
Unowned References
為了解決這個情況,Swift 提供了 unowned reference 這個概念,keyword 為 unowned
,unowned
代表:
雖然我沒有這個 instance 的擁有權(沒有 strong reference),但是我的生命週期跟它一樣。
因此我們程式可以變成這樣:
class Person {
var card: CreditCard?
}
class CreditCard {
unowned let holder: Person
init(holder: Person) {
self.holder = holder
}
}
讓我們來實際操作這個範例,我們新增一個 Person
叫做 "Elsvette"
:
var renters = ["Elsvette": Person()]
customers["Elsvette"]!.saveCard()
用圖片表示:
接著我們把 "Elsvette"
設成 nil
:
var renters = ["Elsvette": Person()]
customers["Elsvette"]!.saveCard()
customers["Elsvette"] = nil
此時因為 Person
沒有 strong reference 了,因此就會被釋放(dealloc),而 CreditCard
的生命週期跟 Person
一樣,因此也一同被釋放(dealloc)。
Using Unowned Reference
Unowned Reference,操作起來就跟 strong reference 一樣,可以直接存取,而不用 wrap 或 unwrap,跟 strong reference 一樣的安全。
Strong, Weak, and Unowned References
在 Swift 中,有 strong, weak 跟 unowned 可以使用:
Strong Reference
Use strong references from owners to the objects they own
Strong 是最常用到的,在一般的情況下都是用 strong。
Weak Reference
Use weak references among objects with independent lifetimes
當不同的 objects 有各自的生命週期,卻又要互相 retain 時,可以用 weak reference。
Unowned Reference
Use unowned references from owned objects with the same lifetime
當不同的 objects 有相同的生命週期,卻又要互相 retain 時,可以用 unowned reference。
小結
在 Swift 中 memory manangment 有著 Automatic, safe, predictable 的優點。當在使用時,要從 object 之間的關係去著手,而不是執著於要用什麼語法才不會造成 retain cycle。
下一篇: Initialization