学习Swift中的自动引用计数

181 阅读8分钟

Swift中的自动引用计数

自动引用计数(ARC)是一种内存管理属性,用于监控和管理应用程序的内存使用。

Swift的内存管理是自动工作的,无需控制。它自动分配或取消分配内存,以使应用程序高效运行。

主要收获

在本教程结束时,读者应该能够理解以下内容。

  1. 什么是ARC,它的功能,以及它如何工作。
  2. 参考周期。
  3. 如何使用Xcode来检测参考周期。

ARC的功能

Swift使用两种方法进行内存管理:初始化init() ,去初始化deinit() 。初始化在Swift中被定义为准备一个类、结构或枚举类型实例的使用。它让用户为每个保存的属性设置默认值。去初始化是指在不再需要类实例时,对其进行去分配的过程。

这可以减少系统使用的内存量。自动引用计数有以下作用。

  1. init() 创建新的类,ARC分配一个内存块来保存新类中的信息。
  2. 当类的实例用完后,deinit() ,为将来的类实例缓存和恢复释放内存。
  3. ARC维护和管理跟踪目前被类实例引用的属性、常量和变量。这样做是为了让deinit() ,只对那些没有被利用的进行处理。
  4. 为了防止类实例运行后的去分配,ARC对这类类实例的属性、常量和变量保持 "强引用"。

ARC是如何工作的

每当一个类的新实例被创建时,ARC都会分配一部分内存空间来保存数据。该内存包含了实例类型的数据,以及所有与之相关的存储属性的内容。

当一个实例变得过时时,ARC会释放该实例所使用的空间,允许它被用于其他用途。这可以防止类实例在不再需要时占用内存。

下面是一个关于ARC如何工作的例子。这个例子从类Student 开始,该类被用来定义一个存储的常量score

    class Student {
    let score: String
    init(score: String) {
        self.score = score
        print("\(score) is being initialized")
    }
    deinit {
        print("\(score) is being deinitialized")
    }
}

Student 类中的一个初始化器创建了该实例的score 属性,并产生一个信号以表明初始化正在进行中。当Student 类的一个实例被取消分配时,它有一个去初始化器,输出一个去分配操作的信息。

引用周期

一个引用周期只是一个或多个元素,它们相互引用的方式,如果用箭头表示它们的依赖关系写在纸上,它们就会有一个 "周期"。在ARC中,有三种类型的引用循环;强引用、弱引用和无主引用。

强引用循环

一个强引用是一个对象,它的去分配不是由ARC完成的。一个强引用循环是一组类的实例,它们可以相互保持强链接,并保持其他实例的运行。

    class Player {
    var name: String
    var emdid: String
    var title: String
    init(inName:String, inEmdid:String, inTitle:String) {
      name = inName
      emdid = inEmdid
      title = inTitle
    }   
    deinit {
         print("Player : \(name) removed");
    }
}
var anderson : Player? = Player(inName: "Anderson", inEmdid: "100", inTitle: "Striker")
anderson = nil    

上面的代码定义了一个名为Player 的类,其变量为name,empid, 和title 。我们构建了一个名为anderson 的类Player 的实例,并将该类的所有数据提供给它。通过这样做,它给了anderson 一个对该对象的强引用。

ARC的内存管理不会去分配任何具有强引用的对象。接下来,我们将对象anderson 的引用置空,从而将它所引用的对象去掉;这就是在去掉对象之前,调用类deinit() 的函数Player

打破强引用循环

在处理类型变量时,Swift提供了两种方法来解决强引用循环。使用弱引用和无主引用。这些引用类型允许引用循环中的一个实例链接到另一个实例,而不对后者保持据点。即使实例之间相互引用,也不会形成强引用循环。

弱的引用循环

弱引用不会对它所引用的对象保持强势的控制。当它这样做时,它不会阻止ARC将其抛弃。这个动作使引用不会形成一个强引用循环。weak 术语在变量声明之前,表示弱引用。

由于弱引用并没有对它所链接的实例保持牢固的控制,因此在弱引用已经指向该实例的情况下,该实例可以被取消分配。因此,一旦它所指向的实例被删除,ARC就会立即将弱引用改为nil 。弱引用通常被表述为变量,而不是任意类型的常量,因为它们的值在执行时可能被改变为nil

下面是使用弱引用打破强引用循环的过程。Goals 是一个由Player 类定义的变量属性。goals 属性是可选的,并且是隐式解包的。Player 类型的常量属性被定义在Goals 类中。它也有一个初始化器,需要一个Player 实例作为参数。

    class Player {

    // MARK: - Properties

    var goals: Goals!

}

class Goals {

    // MARK: - Properties

    weak var player: Player?

    // MARK: - Initialization

    init(player: Player) {
        self.player = player
    }

}

我们将Goals 类的player 属性定义为弱属性,以打破Player 实例和Goals 实例之间的强引用循环。player 属性必须被声明为一个具有可选类型的变量对象,正如下面所做的那样。

    class Player {

    // MARK: - Properties

    var goals: Goals!

}

class Goals {

    // MARK: - Properties

    weak var player: Player?

    // MARK: - Initialization

    init(player: Player) {
        self.player = player
    }

}

未拥有的引用循环

无主引用的行为与弱引用的行为相同。然而,他们不会将保留的计数提高一个。与弱引用不同,无主引用不需要成为一个选项,因为它们在去分配时不会被改变为nil。只有当你确定对象在被设置后不会变成nil时,你才应该使用无主引用。

在前面的例子中,我们有一个强引用循环。一个player 的实例保持对goals 的强引用,而goals 保持对player 的强链接。请记住,属性默认是强的。强引用循环可以通过使Goals 类的播放器属性不被拥有而被打破。

    class Player {

    // MARK: - Properties

    var goals: Goals!

}

class Goals {

    // MARK: - Properties

    unowned var player: Player?

    // MARK: - Initialization

    init(player: Player) {
        self.player = player
    }

}

未拥有的引用总是被认为是有价值的。连接到Player 实例的Goals 实例在Player 实例被删除时被自动删除。只有在player 属性被设置为unowned而不是strong时才允许,这是它的默认值。

如何使用Xcode可视化工具检测引用周期

开发人员利用Xcode为苹果的众多平台创建应用程序。当你想在苹果的软件中识别参考周期时,也可以使用它。下面是一个用于识别参考周期的过程。

第一步:在Xcode中,启动Contacts目录中的Starter项目。创建并运行该项目,应该可以看到以下内容。

Arc 1

上面的截图显示了一个联系人应用程序。

然而,在这个项目中存在一个严重的缺陷。一个参考周期被隐藏在某处。由于泄漏的项目很小,用户在很长一段时间内不会注意到这个问题,而且它们的大小导致泄漏更难追踪。Xcode 10包括一个内置的工具,可以帮助用户找到最微小的泄漏。

第二步:重新构建并启动程序。通过向左滑动两个或三个联系人来删除它们,或者直接点击删除,你可以删除三个联系人。他们不是已经完全消失了吗?

第三步:滚动到Xcode的底部,选择调试内存图选项,因为程序仍在运行。

Arc 2

第四步:在Debug Navigator中,检查Runtime Problems。它们是由紫色的方块识别的,里面有白色的感叹号,就像这里显示的那些。

Arc 3

第五步:在导航器中选择一个受影响的接触对象。中间的循环被带出来。通过相互参照,联系人和数字对象相互保留。这意味着两者之间有一个强大的参考循环。这些问题应该促使用户去检查代码。请观察这样一个事实:一个联系人变量可以在没有数字变量的情况下存在。另一方面,一个数字如果没有联系就不会存在。

结论

当一个对象或一个类的实例不再需要时,它必须被解配。一个对象所持有的内存在去分配时被释放,从而产生更多的空间。这对系统的性能和效率至关重要。ARC可以使iOS应用程序运行得更顺畅。