- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。