[譯] Swift:經過示例避免內存泄漏

在 Swift 中,使用自動引用計數(ARC)來管理 iOS 應用程序中的內存使用狀況。html

每次建立類的新實例時,ARC都會分配一塊內存來存儲有關它的信息,並在再也不須要該實例時自動釋放該內存。前端

做爲開發人員,你不須要爲內存管理作任何事情,除了如下3種狀況,你須要告訴 ARC 有關實例之間關係的更多信息,以免「循環引用」。android

在本文中,咱們將在集中討論這3種狀況,並查看循環引用的實際示例以及如何去避免它們。ios

可是首先,咱們得知道什麼是循環引用以及爲何咱們須要避免它們?git


循環引用:

循環引用就是這種狀況,兩個對象彼此具備強引用並相互持有,ARC 沒法從內存中釋放這些對象從而致使「內存泄漏」。github

在應用程序中出現內存泄漏是很是危險的,由於它們會影響應用程序的性能,而且在應用程序內存不足時可能會致使崩潰。swift


如下三種狀況會形成內存泄漏:

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,因此應該先打印出兩個對象都已分配,而後打印出兩個對象都被銷燬,可是它會打印如下內容:bash

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 的一些重要參考:

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索