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。用圖片來表達就像這樣:

Screen Shot 2014-06-28 at 5.03.37 PM.png


接著我們在新增一個變數(variable):

class BowlingPin {}

func juggle(count: Int) {
   var left = BowlingPin()
  if count > 1 {
       var right = BowlingPin()

就會變成下圖這樣,leftright 分別 retain 一個 instance:

Screen Shot 2014-06-28 at 5.19.35 PM.png


接著我們執行 right = left

class BowlingPin {}

func juggle(count: Int) {
   var left = BowlingPin()
  if count > 1 {
       var right = BowlingPin()
    right = left

leftright 就會 retain 同一個物件了,而原本 right retain 的 BowlingPin instance 就消失了,只剩下一個 BowlingPin instance:

Screen Shot 2014-06-28 at 6.12.33 PM.png


接著,當這個 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
   }
}

接著就建立 ApartmentPerson 的 instance,rentersapts

var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]

用圖片表示:

Screen Shot 2014-06-28 at 6.29.29 PM.png


接下來,假設有 Elsvette 搬進 507

var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]

renters["Elsvette"]!.moveIn(apts[507]!)

用圖片表示:

Screen Shot 2014-06-28 at 6.41.00 PM.png


此時,如果把 renters["Elsvette"] 設為 nil:

var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]

renters["Elsvette"]!.moveIn(apts[507]!)

renters["Elsvette"] = nil

會發現,507: Apartment() 沒有消失,因為他被 Persion 中的 home retain 住了:

Screen Shot 2014-06-28 at 6.44.15 PM.png


此時,再把 apts[507] 設為 nil:

var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]

renters["Elsvette"]!.moveIn(apts[507]!)

renters["Elsvette"] = nil
apts[507] = nil

就會發現,發生 memory leak 了,因為 hometenant 互相 retain,所以無法被釋放:

Screen Shot 2014-06-28 at 6.48.29 PM.png

Weak References

為了避免這個問題,Swift 提供了 weak reference,keyword 為 weakweak 代表一個「弱」連結,它表示說:「當沒有『強』的連結連到我時,我就自動釋放這個 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:

Screen Shot 2014-06-29 at 2.37.40 PM.png


因此,當我們把 renters["Elsvette"] 設為 nil

var renters = ["Elsvette": Person()]
var apts = [507: Apartment()]

renters["Elsvette"]!.moveIn(apts[507]!)

renters["Elsvette"] = nil

renters["Elsvette"] 就會自動被釋放,而 Apartment 中的 tenant: Person? 也會因為沒有「強」連結連著它而自動釋放:

Screen Shot 2014-06-29 at 2.42.51 PM.png

Screen Shot 2014-06-29 at 2.43.14 PM.png

Screen Shot 2014-06-29 at 2.46.38 PM.png

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
   }
}

但是問題來了,我們會發現,這讓 PersionCreditCard 產生了 retain cycle,彼此互相 retain,因此會造成 memory leak。

那我們把 let holder: Person 改成 weak,就會變成 weak var holder: Person?,這也不行呀,因為 holde 不能為 nil,所以不能是 optional 的。我們發現,在這個「信用卡」與「持卡人」的情況下,cardholder 會是同時吋在,且同時消失的。也就是說,它們的 生命週期 是一樣的。

Unowned References

為了解決這個情況,Swift 提供了 unowned reference 這個概念,keyword 為 unownedunowned代表:

雖然我沒有這個 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()

用圖片表示:

Screen Shot 2014-06-29 at 4.07.55 PM.png


接著我們把 "Elsvette" 設成 nil

var renters = ["Elsvette": Person()]
customers["Elsvette"]!.saveCard()
customers["Elsvette"] = nil

Screen Shot 2014-06-29 at 4.09.31 PM.png

此時因為 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