- 原文地址:Swift: Avoiding Memory Leaks by Examples
- 原文做者:jaafar barek
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:LoneyIsError
- 校對者:HearFishle
在 Swift 中,使用自動引用計數(ARC)來管理 iOS 應用程序中的內存使用狀況。html
每次建立類的新實例時,ARC都會分配一塊內存來存儲有關它的信息,並在再也不須要該實例時自動釋放該內存。前端
做爲開發人員,你不須要爲內存管理作任何事情,除了如下3種狀況,你須要告訴 ARC 有關實例之間關係的更多信息,以免「循環引用」。android
在本文中,咱們將在集中討論這3種狀況,並查看循環引用的實際示例以及如何去避免它們。ios
可是首先,咱們得知道什麼是循環引用以及爲何咱們須要避免它們?git
循環引用就是這種狀況,兩個對象彼此具備強引用並相互持有,ARC 沒法從內存中釋放這些對象從而致使「內存泄漏」。github
在應用程序中出現內存泄漏是很是危險的,由於它們會影響應用程序的性能,而且在應用程序內存不足時可能會致使崩潰。swift
假設咱們有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
關鍵字來標記無主引用。
內存泄漏的另外一個緣由多是協議和類之間的密切關係。在下面的示例中,咱們將採用一個真實的場景,咱們有一個 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
複製代碼
假設咱們有如下 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 來避免循環引用。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。