deinit
weak
針對 Optional 的變數、常數unowned
針對非 Optional 的變數、常數Function 的參數,執行區塊{}
裡面的變數、常數會隨著執行任務結束而釋放記憶體空間。
func foo(argument: Int) {
var variable: Int = 1
let constant: Int = 2
}
foo(0)
//釋放 arg, variable, constant 記憶體
Struct 區塊{}
裡面的變數、常數會因為 Struct 產生出來的變數不再被參考到而釋放記憶體空間。
struct Bar {
var variable: Int = 1
let constant: Int = 2
}
var bar: Bar? = Bar()
bar = nil //釋放 variable, constant 記憶體
同樣的,Class 區塊{}
裡面的變數、常數也會因為 Class 產生出來的變數不再被參考到而釋放記憶體空間。
class Fiz {
var variable: Int = 1
let constant: Int = 2
}
var fiz: Fiz? = Fiz()
fiz = nil //釋放 variable, constant 記憶體
deinit
deinit{}
負責處理在類別消除前執行一些動作,我們可以利用這個函式觀察物件的生命週期。
class Person {
var name: String
init(name: String) {
self.name = name
println("\(name) is borned")
}
deinit {
println("\(name) is dead")
}
}
var man: Person? = Person(name: "Hemingway") //output: Hemingway is borned
man = nil //output: Hemingway is dead
Swift 透過 ARC 記憶體管理回收沒有被參考到的變數、常數,但有些情形會是物件間相互參考,雖然失去外部參考沒有人可以存取,但記憶體始終不會被釋放。如下範例:
class Person {
var name: String
var lover: Person?
init(name: String) {
self.name = name
println("\(name) is borned")
}
deinit {
println("\(name) is dead")
}
}
var boy: Person! = Person(name: "Roméo") //output: Roméo is borned
var girl: Person! = Person(name: "Juliette") //output: Juliette is borned
boy.lover = girl //return: {name "Roméo" {{name "Juliette" nil}}}
girl.lover = boy //return: {name "Juliette" {{name "Roméo" {{…}}}}}
boy = nil
girl.lover //return: {name "Roméo" {{name "Juliette" {{…}}}}}
girl = nil
boy.lover = girl
、girl.lover = boy
,物件形成相互參考。boy = nil
,卻因為女孩依然記得他girl.lover = boy
,男孩沒被系統回收。gilr = nil
,卻因為死去的男孩依然也記得她boy.lover = girl
,女孩沒被系統回收。weak
針對 Optional 的變數、常數物件永不消滅,這不科學啊!這是記憶體洩漏 (Memory Leak),記憶體用不著抓著不放會導致 App 消耗越來越多的記憶體,最後系統因為記憶體資源不足必須刪除進程,佔用大量記憶體的程式就是開刀對象啦。
那麼之前的問題要如何解決呢?答案是將有可能造成 Reference Cycle 的變數宣告為 Optional 並加上weak
關鍵字,告訴系統這個變數不會增加 ARC 參考數量。
class Person {
var name: String
weak var lover: Person?
init(name: String) {
self.name = name
println("\(name) is borned")
}
deinit {
println("\(name) is dead")
}
}
var boy: Person! = Person(name: "Roméo") //output: Roméo is borned
var girl: Person! = Person(name: "Juliette") //output: Juliette is borned
boy.lover = girl //return: {name "Roméo" {{name "Juliette" nil}}}
girl.lover = boy //return: {name "Juliette" {{name "Roméo" {{…}}}}}
boy.lover?.name //return: "Juliette"
girl.lover?.name //return: "Roméo"
boy = nil //output: Roméo is dead
girl.lover?.name //return: nil
girl = nil //output: Juliette is dead
boy.lover = girl
、girl.lover = boy
,但因為weak
物件不會形成相互參考。boy = nil
,女孩馬上忘記這段感情girl.lover?.name //return: nil
,男孩被系統回收。gilr = nil
,因為世界上沒有人記得她,女孩也被系統回收。unowned
針對非 Optional 的變數、常數另一種情況是物件所參考的對象在物件生成的時候就存在,直到物件消滅。
以下範例Slave
一出生就確定跟隨Master
,而Master
可以在其生命週期中任意更換Slave
。
class Slave {
let master: Master
init(master: Master) {
self.master = master
println("Slave init")
}
deinit {
println("Slave deinit")
}
}
class Master {
var slave: Slave?
init() {
slave = Slave(master: self)
println("Master init")
}
deinit {
println("Master deinit")
}
}
var master: Master! = Master() //output: Slave init -> Master init
master = nil //Slave don't let Master die!!!
Slave
會記住Master
,所以就算主人死去master = nil
,兩個人還是沒有從記憶體中移除。試試把Master
中對於Slave
的參考設為weak
,看看發生什麼事情。
class Slave {
let master: Master
init(master: Master) {
self.master = master
println("Slave init")
}
deinit {
println("Slave deinit")
}
}
class Master {
weak var slave: Slave?
init() {
slave = Slave(master: self)
println("Master init")
}
deinit {
println("Master deinit")
}
}
var master: Master! = Master() //output: Slave init -> Slave deinit -> Master init
master.slave //return: nil
master = nil //outout: Master deinit
weak
的關係,所以一出生就被系統回收。master.slave //return: nil
。這個情況下,不是Master
對Slave
參考保持弱連結,而是Slave
不允許保有對Master
的強參考,所以Slave
對Master
的關係是永遠忠誠(let
)卻不擁有(unowned
)。
class Slave {
unowned let master: Master
init(master: Master) {
self.master = master
println("Slave init")
}
deinit {
println("Slave deinit")
}
}
class Master {
var slave: Slave?
init() {
slave = Slave(master: self)
println("Master init")
}
deinit {
println("Master deinit")
}
}
var master: Master! = Master() //output: Slave init -> Master init
master = nil //output: Master deinit -> Slave deinit
Master
誕生前系統趕緊配給Slave
。Master
死去後系統馬上回收Slave
。誰說一個Master
一輩子只能配一個Slave
,下面範例展示更換Slave
的過程。
class Slave {
static var count: Int = 0
unowned let master: Master
var number: Int
init(master: Master) {
self.master = master
self.number = ++Slave.count
println("Slave\(self.number) init")
}
deinit {
println("Slave\(self.number) deinit")
}
}
class Master {
var slave: Slave?
init() {
slave = Slave(master: self)
println("Master init")
}
deinit {
println("Master deinit")
}
}
var master: Master! = Master() //output: Slave1 init -> Master init
master.slave = Slave(master: master) //output: Slave2 init -> Slave1 deinit
master.slave = Slave(master: master) //output: Slave3 init -> Slave2 deinit
master = nil //output: Master deinit -> Slave3 deinit
Master
誕生前系統趕緊配給Slave1
。Master
想換個奴隸,系統配給Slave2
並回收Slave1
。Master
想換個奴隸,系統配給Slave3
並回收Slave2
。Master
死去後系統馬上回收Slave3
。還有一種 Reference Cycle 的情況是由 Closure 造成。請見範例。
class Foo {
let name = "Foo"
init() {
println("Foo init")
}
deinit {
println("Foo deinit")
}
lazy var info: ()->() = {
println("my name is \(self.name)")
}
}
var foo: Foo! = Foo() //output: Foo init
foo.info() //output: my name is Foo
foo = nil //foo is keeped by closure
info
是Foo
屬性,型別為()->()
,透過lazy
關鍵字延遲初始化時機,一直到被存取前才完成初始化。info
放在init()
裡面初始化的理由是:類別必須先完成所有屬性初始化才能存取本身的屬性,但這個 Closure 裡面卻有存取self.name
的 statement,會造成編譯失敗。foo.info()
的時候,Closureinfo
保存self
的參考。foo
不再被外部參考,記憶體應該被釋放時,卻因為self
被info
參考無法釋放記憶體,造成 Reference Cycle。解決方式是在 Closure 中將宣告[unowned self]
,意味雖然 Closure 整個生命週期都可以存取到self
,但不擁有self
。範例如下:
class Foo {
let name = "Foo"
init() {
println("Foo init")
}
deinit {
println("Foo deinit")
}
lazy var info: ()->() = {
[unowned self] in
println("my name is \(self.name)")
}
}
var foo: Foo! = Foo() //output: Foo init
foo.info() //output: my name is Foo
foo = nil //output: Foo deinit
[unowned self]
要放在 Closure 最前面,後面加上in
。foo=nil
失去外部參考,Closure 也不會參考self
,foo
的資源會系統釋放。最後再看一個更複雜的案例。doCapture
這個 Closure 捕捉三個物件:
self
- 物件本身在 Closure 的生命週期中不會消失,使用unowned self
。foo
- 在 Closure 產生後可能被設為nil
,使用weak foo = self.foo
。bar
- 物件初始化過程擁有的物件參考,在 Closure 的生命週期中不會變更或消失,使用unowned bar = self.bar
。class Foo {
let name = "Foo"
deinit { println("Foo is dead") }
}
class Bar {
let name = "Bar"
deinit { println("Bar is dead") }
}
class Qiz {
let name = "Qiz"
var foo: Foo?
let bar: Bar
init() {
foo = Foo()
bar = Bar()
}
deinit {
println("Qiz is dead")
}
lazy var doCapture: ()->() = {
[unowned self, weak foo = self.foo, unowned bar = self.bar] in
println("\(self.name), \(foo?.name), \(bar.name)")
}
}
var qiz: Qiz! = Qiz() //return: {name "Qiz" {{name "Foo"}} {name "Bar"} nil}
qiz.doCapture() //output: Qiz, Optional("Foo"), Bar
qiz = nil //output: Qiz is dead -> Foo is dead -> Bar is dead
以下歸納使用weak
與unowned
的規則:
"you must ... use the in keyword, even if you omit the parameter names, parameter types, and return type"
如果參考的物件或屬性為 Optional,使用weak
。
nil
),使用unowned
。in
關鍵字,即使忽略參數名稱、參數型別、回傳型別。