開始講 retain cycle 前,先來講解 Swift 中記憶體的回收機制 ARC(Automatic Reference Counting)。
在 iOS 中每個 App 在執行時皆會被自動配置一定數量的記憶體好讓 App 內所宣告的物件能正常使用,當物件的生命週期結束時會參照 reference count 來將該物件所佔用之記憶體數量釋放回 App 所能使用之記憶體總量中。
在開發任何程式時一定會遇到一個相同的問題: Memory Leak。造成 memory leak 的主因是開發過程一直跟系統要求動態配置記憶體卻沒有進行記憶體釋放,導致記憶體被強制佔用直到整個程式生命週期結束(包含正常結束與被強制結束)。雖然 Swift 中會透過 ARC 來輔助釋放記憶體減輕開發者的負擔,但實際上仍常出現 memory Leak。
這邊舉個因 retain cycle 而導致 memory Leak 的例子:
宣告兩個物件 People 跟 Macbook:
class People {
let name: String
var macbook: Macbook?
init(name: String, macbook: Macbook?) {
self.name = name
self.macbook = macbook
}
deinit {
print("\(name) is being deinitialized.")
}
}
class Macbook {
let name: String
var owner: People?
init(name: String, owner: People?) {
self.name = name
self.owner = owner
}
deinit {
print("Macbook named \(name) is being deinitialized.")
}
}
初始化宣告好的物件:
var gavin: People? = People(name: "Gavin", macbook: nil)
var computer: Macbook? = Macbook(name: "Matilda", owner: nil)
先來將兩個物件 deinit 確定會不會產生 retain cycle:
gavin = nil
// print "Gavin is being deinitialized."
computer = nil
// print "Macbook named Matilda is being deinitialized."
因可正常印出 deinit()的內容所以可以確定沒有 retain cycle 的情況發勝。接下來要讓兩個物件彼此 reference 來產生 retain cycle:
gavin?.macbook = computer
computer?.owner = gavin
經過上一步驟後在將兩物件分別 deinit,此時是否會印出文字? 答案是.....什麼都沒印出。
上述的行為中因兩物件會彼此 reference 最後導致了 memory leak。
解決上述問題的方法很簡單,只需在會產生相互 reference 的 property 加上 weak 屬性即可解決此問題。
class Macbook {
let name: String
weak var owner: People?
init(name: String, owner: People?) {
self.name = name
self.owner = owner
}
deinit {
print("Macbook named \(name) is being deinitialized.")
}
}
Reference
Automatic Reference Counting
第一次宣告var owner: Human?
應該為var owner: People?
才是
Thanks for correcting.