- 原文地址:Swift: Avoiding Memory Leaks by Examples
- 原文作者:jaafar barek
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:LoneyIsError
- 校對者:HearFishle
在 Swift 中,使用自動引用計數(ARC)來管理 iOS 應用程式中的記憶體使用情況。
每次建立類的新例項時,ARC都會分配一塊記憶體來儲存有關它的資訊,並在不再需要該例項時自動釋放該記憶體。
作為開發人員,你不需要為記憶體管理做任何事情,除了以下3種情況,你需要告訴 ARC 有關例項之間關係的更多資訊,以避免「迴圈引用」。
在本文中,我們將在集中討論這3種情況,並檢視迴圈引用的實際示例以及如何去避免它們。
但是首先,我們得知道什麼是迴圈引用以及為什麼我們需要避免它們?
迴圈引用:
迴圈引用就是這種情況,兩個物件彼此具有強引用並相互持有,ARC 無法從記憶體中釋放這些物件從而導致「記憶體洩漏」。
在應用程式中出現記憶體洩漏是非常危險的,因為它們會影響應用程式的效能,並且在應用程式記憶體不足時可能會導致崩潰。
以下三種情況會造成記憶體洩漏:
1- 兩個類之間的強引用:
假設我們有2個類(Author 類和 Book 類)直接相互引用:
class Author {
var name:String
var book:Book
init(name:String,book:Book) {
self.name = name
self.book = book
print("Author Object was allocated in memory")
}
deinit {
print("Author Object was de allocated")
}
}
var author = Author(name:"John",book:Book())
author = nil
複製程式碼
class Book {
var name:String
var author:Author
init(name:String,author:Author) {
self.name = name
self.author = author
print("Book object was allocated in memory")
}
deinit {
print("Book Object was deallocated")
}
}
var book = Book(name:"Swift",author:author)
book = nil
複製程式碼
理論上,因為這兩個物件都被設定為 nil,所以應該先列印出兩個物件都已分配,然後列印出兩個物件都被銷燬,但是它會列印以下內容:
Author Object was allocated in memory
Book object was allocated in memory
複製程式碼
正如你所見,兩個物件並未從記憶體中釋放,因為當兩個物件之間彼此具有強引用時發生了迴圈引用。
為了解決這個問題,我們可以如下宣告弱引用或無主引用:
class Author {
var name:String
weak var book:Book? // book 物件需要被宣告為弱的可選項
init(name:String,book:Book?) {
self.name = name
self.book = book
print("Author Object was allocated in memory")
}
deinit {
print("Author Object was deallocated")
}
}
複製程式碼
這次兩個物件都會被釋放,控制檯將列印以下內容:
Author Object was allocated in memory
Book object was allocated in memory
Author Object was deallocated
Book Object was deallocated
複製程式碼
問題解決了,ARC 在清理記憶體塊時可以通過使其中一個引用變弱來釋放物件,但弱引用和無主引用是什麼呢?根據 apple 的文件:
弱引用
弱引用是一種不會強制保留它引用例項的引用,因此就不會阻止 ARC 處理這些的例項。這樣使引用避免了成為強引用迴圈的一部分。你可以通過在屬性或變數宣告之前放置
weak
關鍵字來標記弱引用。
無主引用
與弱引類似,無主引用 也不會對它引用的例項保持強引用。然而,與弱引用不同得是,當另一個例項具有相同的生命週期或更長的生命週期時,則需要使用無主引用。 你可以通過在屬性或變數宣告之前放置
unowned
關鍵字來標記無主引用。
2- 類協議關係:
記憶體洩漏的另一個原因可能是協議和類之間的密切關係。在下面的示例中,我們將採用一個真實的場景,我們有一個 TablViewController 類和一個 TableViewCell 類,當使用者按下 TableViewCell 中的一個按鈕時,它應該將此動作代理給 TablViewController,如下所示:
@objc protocol TableViewCellDelegate {
func onAlertButtonPressed(cell:UITableViewCell)
}
class TableViewCell: UITableViewCell {
var delegate:TableViewCellDelegate?
@IBAction func onAlertButtonPressed(_ sender: UIButton) {
delegate?.onAlertButtonPressed(cell: self)
}
}
複製程式碼
class TableViewController: UITableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.delegate = self
return cell
}
deinit {
print("TableViewController is deallocated")
}
}
extension TableViewController: TableViewCellDelegate {
func onAlertButtonPressed(cell: UITableViewCell) {
if let row = tableView.indexPath(for: cell)?.row {
print("cell selected at row: \(row)")
}
dismiss(animated: true, completion: nil)
}
}
複製程式碼
通常,當我們關閉 TableViewController 時,ARC 應該呼叫 deinit 方法並且在控制檯中 列印「TableViewController is deallocated」,但是在這種情況下,由於 TableViewCellDelegate 和 TableViewController 彼此之間具有強引用,所以它們永遠不會從記憶體中釋放。
為了解決這個問題,我們可以簡單地將 TableViewCell 類調整為如下:
@objc protocol TableViewCellDelegate {
func onAlertButtonPressed(cell:UITableViewCell)
}
class TableViewCell: UITableViewCell {
weak var delegate:TableViewCellDelegate?
@IBAction func onAlertButtonPressed(_ sender: UIButton) {
delegate?.onAlertButtonPressed(cell: self)
}
}
複製程式碼
這次關閉 TableViewController 就可以在控制檯中看到:
TableViewController is deallocated
複製程式碼
3- 閉包的強迴圈引用:
假設我們有以下 ViewController:
class ViewController: UIViewController {
var closure : (() -> ()) = { }
override func viewDidLoad() {
super.viewDidLoad()
closure = {
self.view.backgroundColor = .red
}
}
deinit {
print("ViewController was deallocated")
}
}
複製程式碼
嘗試關閉 ViewController,deinit 方法永遠不會被執行。 這是因為閉包捕獲了 ViewController 的強引用。要解決這個問題,我們需要在閉包中使用 weak 或 unowned 修飾的 self,如下所示:
class ViewController: UIViewController {
var closure : (() -> ()) = { }
override func viewDidLoad() {
super.viewDidLoad()
closure = { [unowned self] in
self.view.backgroundColor = .red
}
}
deinit {
print("ViewController was deallocated")
}
}
複製程式碼
這次關閉 ViewController 時控制檯將列印:
ClosureViewController was deallocated
複製程式碼
總結
毫無疑問,ARC 對應用程式的記憶體管理起了了不起的作用,我們開發者所要做的是注意類之間,類和協議之間以及內部閉包之間的強引用,通過宣告 weak 或者 unowned 來避免迴圈引用。
關於 ARC 的一些重要參考:
- Apple 官方文件
- Raywenderlich 關於 ARC 的文章。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。