首页 iOS.& Swift Books Swift学徒

23
内存管理 由cosminpupăză撰写

您探讨了第14章“高级类”中的基本内存管理,当您探索了班级生命周期和 自动参考计数 (弧)。在大多数情况下,Swift中的内存管理从盒子里工作,几乎没有努力。

但是,存在弧形无法推断对象之间正确的关系时的情况。那就是你进来的地方。

在本章中,您将重新审视概念 参考循环 并了解课程和封闭的解决它们。您还将学习如何使用 捕获清单 在闭包中捕获封闭范围的值。在本章末尾,您将掌握打破参考周期的艺术,但在您到达那一点之前,您将首先学习它们是如何形成的。

类的参考周期

一个持有的两个阶级实例 强大的参考 彼此创造一个 强大的参考周期 这导致了一个 内存泄漏。那是因为每个实例都保持另一个活动,因此他们的参考数数从未达到零。

例如,我们的网站有一座顶级编程教程山,其中大部分是在看到它之前由编辑仔细审查。您可以使用以下类模拟这些教程:

class Tutorial {
  let title: String
  var editor: Editor?

  init(title: String) {
    self.title= title
  }

  deinit {
    print("Goodbye tutorial \(title)!")
  }
}

除了标题问题之外,教程可能有一个编辑器,所以它标记为可选。请记住,从第14章“高级课程”,Swift调用 Deinitializer. 在它从内存中释放对象之前自动右转,其参考计数变为零。

Now that you’ve defined an editor for each tutorial, you need to declare an Editor class, like so:

class Editor {
  let name: String
  var tutorials: [Tutorial] = []

  init(name: String) {
    self.name = name
  }

  deinit {
    print("Goodbye editor \(name)!")
  }
}

Each editor has a name and a list of tutorials they have edited. The tutorials property is an array so you can add to it.

现在定义出版的全新教程和编辑器,以确保它符合我们的高标准:

do {
  let tutorial = Tutorial(title: "Memory management")
  let editor = Editor(name: "Ray")
}

这 se are placed in a scope (created with do {}) so that as soon as they go out of scope the references to them are dropped and they are correctly deallocated. Everything is working fine.

当你改变两个对象之间的关系时,会发生一些事情:

do {
  let tutorial = Tutorial(title: "Memory management")
  let editor = Editor(name: "Ray")
  tutorial.editor = editor
  editor.tutorials.append(tutorial)
}

虽然两个物体都脱离了范围,但没有叫做Deinitializers,并且没有打印到控制台 - Bummer!那是因为您刚刚在教程和相应编辑器之间创建了参考周期。即使您不再需要它们,您也不会从内存中释放对象。

现在您了解参考周期的发生方式,您可以打破它们。救援的弱点!

薄弱的参考文献

薄弱的参考文献 参考文献不在中发挥作用 所有权 一个物体。关于使用它们的伟大事物是它们会自动检测底层对象消失的时候。这就是他们的原因 总是 declared with an optional type. They become nil once the reference count reaches zero.

weak var editor: Editor?
Goodbye editor Ray!
Goodbye tutorial Memory management!

突出的参考文献

您有另一种方法可以打破参考周期: 突出的参考文献,这表现了很多弱者,因为它们不会改变对象的参考数。

class Tutorial {
  let title: String
  let author: Author  
  weak var editor: Editor?

  init(title: String, author: Author) {
    self.title= title
    self.author = author
  }
  
  deinit {
    print("Goodbye tutorial \(title)!")
  }
}
class Author {
  let name: String
  var tutorials: [Tutorial] = []

  init(name: String) {
    self.name = name
  }

  deinit {
    print("Goodbye author \(name)!")
  }
}
do {
  let author = Author(name: "Cosmin")
  let tutorial = Tutorial(title: "Memory management", 
                          author: author)
  let editor = Editor(name: "Ray")                         
  author.tutorials.append(tutorial)
  tutorial.editor = editor
  editor.tutorials.append(tutorial)
}
class Tutorial {
  unowned let author: Author
  // original code
}
Goodbye editor Ray!
Goodbye author Cosmin!
Goodbye tutorial Memory management!

用于闭包的参考循环

您在第8章中学到了“使用闭包的集合迭代”,闭合封闭范围的捕获值。由于Swift是一种安全语言,所以封闭延长了他们使用的任何对象的寿命,以便保证这些对象是活着的并且有效。此自动安全性很好,但如果延长本身捕获关闭的对象的生命周期,则您可以无意中创建参考周期。关闭,您看到,是引用类型本身。

lazy var description: () -> String = {
  "\(self.title) by \(self.author.name)"
}
print(tutorial.description())

捕获清单

捕获清单 是一种语言功能,可以帮助您控制闭包如何扩展它引用的对象的生命周期。简单地,它们是封闭捕获的变量列表。捕获列表显示在任何参数之前关闭的开始。

var counter = 0
var f = { print(counter) }
counter = 1
f()
counter = 0
f = { [c = counter] in print(c) }
counter = 1
f()
counter = 0
f = { [counter] in print(counter) }
counter = 1
f()

令人伤害的自我

这 closure that determines the tutorial’s description captures a strong reference of self and creates a reference cycle. Since the closure doesn’t exist after you release the tutorial object from memory, self will never be nil, so you can change the strong reference to an unowned one using a capture list.

lazy var description: () -> String = {
  [unowned self] in
  "\(self.title) by \(self.author.name)"
}
Memory management by Cosmin
Goodbye editor Ray!
Goodbye author Cosmin!
Goodbye tutorial Memory management!

自我弱

这 re are certain times when you can’t capture self as an unowned reference, because it might become nil. Consider the following example:

let tutorialDescription: () -> String
do {
  let author = Author(name: "Cosmin")
  let tutorial = Tutorial(title: "Memory management", 
                          author: author)
  tutorialDescription = tutorial.description
}
print(tutorialDescription())
lazy var description: () -> String = {
  [weak self] in
  "\(self?.title) by \(self?.author.name)"
}
nil by nil

强弱模式

强弱模式 also does not extend the lifetime of self but converts the weak reference to a strong one after it enters the closure:

lazy var description: () -> String = {
    [weak self] in
    guard let self = self else {
      return "这  tutorial is no longer available."
    }
    return "\(self.title) by \(self.author.name)"
}

挑战

在继续前进之前,以下是测试内存管理知识的一些挑战。如果您尝试自己解决这些问题,最好是,如果您陷入困境,则可以使用解决方案。这些随下载或在介绍中列出的印刷书的源代码链接中提供。

挑战1:打破周期

在以下代码中打破强大的参考周期:

class Person {
  let name: String
  let email: String
  var car: Car?

  init(name: String, email: String) {
    self.name = name
    self.email = email
  }

  deinit {
    print("Goodbye \(name)!")
  }
}

class Car {
  let id: Int
  let type: String
  var owner: Person?

 init(id: Int, type: String) {
   self.id = id
   self.type = type
 }

 deinit {
   print("Goodbye \(type)!")
 }
}

var owner: Person? = Person(name: "Cosmin", 
                            email: "[email protected]")
var car: Car? = Car(id: 10, type: "BMW")

owner?.car = car
car?.owner = owner

owner = nil
car = nil

挑战2:打破另一个循环

在以下代码中打破强大的参考周期:

class Customer {
  let name: String
  let email: String
  var account: Account?

  init(name: String, email: String) {
    self.name = name
    self.email = email
  }

  deinit {
    print("Goodbye \(name)!")
  }
}

class Account {
  let number: Int
  let type: String
  let customer: Customer

  init(number: Int, type: String, customer: Customer) {
    self.number = number
    self.type = type
    self.customer = customer
  }

  deinit {
    print("Goodbye \(type) account number \(number)!")
  }
}

var customer: Customer? = Customer(name: "George", 
                                   email: "[email protected]")
var account: Account? = Account(number: 10, type: "PayPal", 
                                customer: customer!)

customer?.account = account

account = nil
customer = nil

关键点

  • 用一个 薄弱参考 to break a 强大的参考周期 if a reference may become nil at some point in its lifecycle.
  • 用A. 招募的参考 您知道参考时打破强大的参考周期 总是 有一个值和意志 绝不 be nil.
  • 必须 use self inside a closure’s body. This is the way the Swift compiler hints to you that you need to be careful not to make a circular reference.
  • 捕获清单 定义如何捕获闭包中的值和引用。
  • 强弱模式 转换对强者的弱引用。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。