Swift内存管理–自动引用计数

时间:2020-02-23 14:44:02  来源:igfitidea点击:

在本教程中,我们将介绍Swift内存管理,并了解Swift中的自动引用计数(ARC)。
这是必不可少的概念,对防止我们的iOS应用程序中的内存泄漏很有用。

Swift内存管理–自动引用计数

自动引用计数是一种基础工具,可用于为引用分配和释放内存。
它跟踪对象的引用计数。

如何分配?初始化对象后,ARC会为其分配一块内存。
该内存保存有关该对象的信息,例如其属性,常量,链接到的引用。
进一步阅读–快速初始化功能。

如何分配? ARC会记录对对象的引用数量。
存在引用时,它无法取消分配对象,因为这可能导致运行时崩溃。
一旦引用计数为0,它将通过调用deinit()释放对象并释放内存。

注意:ARC和内存管理不能保证在XCode Playgrounds中像在XCode项目中那样完美地工作。
不过,由于本文的重点是ARC,而不是iOS应用程序,为方便起见,我们在下面使用游乐场。

Swift ARC实战

让我们创建一个快速的类,然后实例化它。

import Foundation

class Student {
  let name: String
  var website  = "theitroad"
  init(name: String) {
      self.name = name
      print("\(name) is being initialized")
  }
  deinit {
      print("\(name) is being deinitialized")
      print("\(website) is being deinitialized")
  }
}
var s : Student? = Student(name: "Anupam")
var reference = s //reference count is 2
var reference2 = reference //reference count is 3
//prints
//Anupam is being initialized

要取消分配对象,我们需要删除引用。

reference = nil
s = nil
reference2 = nil
//prints
//Anupam is being deinitialized
//theitroad is being deinitialized

在上面的代码中,deinit块由ARC自动运行。
您无法手动调用它。
我们创建了类型为Optional的引用,以便将来调用它们时不会导致崩溃。
将引用设置为nil可使ARC更改引用计数。

注意:ARC不是垃圾收集器。
ARC仅可清除未使用的内存。
在某些情况下,程序员需要帮助ARC清除内存并防止内存泄漏,这将在下一部分中看到。

解决强大的参考周期

默认情况下,ARC创建强引用。
现在,我们可以在场景中存在参考循环,如下面的代码所示。

class Student {
  let name: String
  init(name: String) { self.name = name }
  var university: University?
  deinit { print("\(name) is being deinitialized") }
}

class University {
  let uniName: String
  init(uniName: String) { self.uniName = uniName }
  var student: Student?
  deinit { print("University \(unit) is being deinitialized") }
}

每个类都有对另一个的引用。
让我们初始化它们。

var me: Student?
var uni: University?

me = Student(name: "John Doe")
uni = University(uniName: "IIT")

me?.university = uni
uni?.student = me

这在两个类之间创建了强大的参考。
下图正确描绘了一些东西。

让我们看看强引用如何影响对象的生命周期。

me=nil
uni=nil

//Does nothing

我们已将引用设置为nil,以使ARC取消分配它们,但不会发生。
上面的代码中创建了强引用循环,这可能是内存泄漏的潜在原因!

在解决强引用之前,我们先来看一下其他类型的引用。

强,弱,无主引用

  • 强引用是默认情况下创建的标准引用类型。

  • 弱引用允许创建实例,但不将ARC的引用计数加起来。
    通常,参考寿命短于其他参考时,应将其标记为弱。

  • 无主引用类似于弱引用,不同之处在于它们用于寿命比其他引用更长的引用。

  • 仅当您绝对确定该引用确实存在时,才应使用无主引用。
    否则,如果在无引用上调用该引用,则会崩溃。

  • 当引用为零时,可以使用弱引用。

解决强大的参考周期

在上面的示例中,我们可以通过将任意一个引用设为弱引用或者无主引用来解决强引用周期。
此类引用不会添加到ARC中的引用计数中。

class Student {
  let name: String
  init(name: String) { self.name = name }
  var university: University?
  deinit { print("\(name) is being deinitialized") }
}

class University {
  let uniName: String
  init(uniName: String) { self.uniName = uniName }
  weak var student: Student?
  deinit { print("University \(uniName) is being deinitialized") }
}

初始化引用,然后将其设置为nil现在将调用deinit语句。

var me: Student?
var uni: University?

me = Student(name: "John Doe")
uni = University(uniName: "IIT")

me?.university = uni
uni?.student = me

me=nil
uni=nil

//prints
//John Doe is being deinitialized
//University IIT is being deinitialized

注意:由于无法将无主引用设置为nil,因此在上面的示例中设置无主引用会导致崩溃。

使用捕获列表解决封闭中的参考循环

闭包是参考类型。
因此,它们可以创建一个强大的参考周期,如下所示:

class User {
  
  let name: String
  let skill: String
  
  lazy var summary: () -> String = {
      return "\(self.name) (\(self.skill))"
  }
  
  init(name: String, skill: String) {
      self.name = name
      self.skill = skill
  }
  
  deinit {
      print("Deallocated User")
  }
  
}

让我们分配和取消分配对象。

var name: User? = User(name: "Anupam", skill: "Swift")

name?.summary()

name = nil

//deinit NOT called.

因为闭包是引用类型,所以它们通过捕获"自我"引用来创建强大的引用周期。
因此,参考计数永远不能为零。
让我们尝试解决这个问题。

捕获列表

我们可以将self用作闭包内部的弱引用或者无主引用,如下所示。

lazy var summary: () -> String = {[unowned self] in
      return "\(self.name) (\(self.skill))"
  }

我们明确地传递了一个列表,该列表传递了自己的无主引用。

从闭包中我们知道,参数通过in关键字与返回类型分开。
这样就解决了封闭中的强大参考周期。