Swift記憶體管理,weak和unowned關鍵字
Swift 中的記憶體管理
在 Swift 中,記憶體管理是系統自動控管的,意思是我們不需要再擔心記憶體的分配與釋放等等。當我們透過初始化建構子創建一個物件時, Swift會替我們管理和分配記憶體。而釋放的原則遵循了 Automatic Reference Counting (ARC) 的規則,當一個物件沒有 Reference 的的時後,記憶體會被自動回收。我們只需要保證在適合的時候將其 Referenc 設置為空,就可以確保記憶體使用不會出現 memory leak
的問題。
關於循環引用 retain cycle
所有類似 ARC 的自動引用計數機制都會有一個理論上絕對會出現的問題,那就是會出現 retain cycle 的情況。
這邊為了更清晰的解釋 retain cycle 的一般情況,假設我們有兩個 class A 跟 B,它們之中分別有一個變數屬性持有雙方
1 | class A { |
在 A 的初始化方法中, 我們生成了一個 B 的實體並將其存儲在變數屬性中。然後我們又將 A 的實體賦值給了b.a。 這樣a.b 和 b.a 將在初始化的時候形成一個 retain cycle
。現在當有其他地方初始化了 A,然後即使將其釋放, A 和 B 兩個的 deinit
的方法也不會被呼叫,說明它們並沒有被釋放。
1 | var obj: A? = A() |
因為即使 obj
不再持有 A 的這個對象,b 中的 b.a
依然引用著這個物件, 導致它無法被釋放。而進一步來說 a 中也持有著 b ,導致 b 也無法被釋放。在 obj
設為 nil 之後, 我們就再也無法獲取對於這個物件的引用了, 這樣除非是刪除整個應用程序,否則我們永遠也無法將它釋放了,而造成所謂的memory leak
。
在 Swift 裡防止 retain cycle
為了防止memory leak
的悲劇發生,我們必須讓編譯器知道,表示我們不希望他們互相引用。一般來說我們習慣希望 被動
的一方不要去持有主動
的一方。在這個範例中 b.a 對 A 的實體引用是由 A 的方法設定的, 我們在之後直接使用的也是 A 的實體,因此我們可以將 class B 的代碼改為
1 | class B { |
在 var a
前面加上了 weak
關鍵字,向編譯器說明我們希望引用 a 卻不希望增加 a 的引用計數。於是乎當 obj 指向 nil ,整個對 A 這個 實體的引用計數就歸零了,於是這個實體物件可以釋放, 接著這個 A 實體中對 B 的引用也隨著這次釋放結束了作用範圍, 所以 b 的引用計數也歸零而得到釋放。添加 weak 後的輸出
1 | A deinit |
weak 與 unowned 的差異
unkowned
跟 weak
相同之處都是在於不會增加引用對象的引用計數。然後我們需要在什麼時候使用 weak
什麼時候使用 unkowned
呢? 在 Apple 官方文檔中寫著
IMPORTANT
Use an unowned reference only when you are sure that the reference always refers to an instance that has not been deallocated.
If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.
當一個引用在其生命週期中變為 nil 時依然合理,就把這個引用定義為 weak
。相反,如果你事先知道一個引用在設置好了之後不會再變成 nil,就把它定義成 unowned
1 | class RetainCycle { |
上面的例子裡,閉包用一般引用的形式引用了 self , 同時 self 透過自己的閉包屬性也保留了一個對該閉包的引用,就這造成了 retain cycle。
在這種情況下,我們就可以使用 unowned
關鍵字。
1 | closure = { [unowned self] in |