iOS 应用程序中的内存管理2025年03月17日 | 阅读 9 分钟 在当今科技世界,没有内存的增强是不可想象的。内存包括用于在设备上存储信息的硬件。就 iPhone 而言,它使用两种主要方式存储数据:1. 磁盘,2. 随机存取存储器。但是,当应用程序在 iPhone 设备上运行时,一个包含所有可执行指令的文件会被加载到RAM 中。 应用程序还需要一些内存来维护堆(heap),因为我们类的所有对象都驻留在其中。内存管理就是管理堆内存的过程。它包括堆对象的生命周期管理,以便在不再需要它们时释放对象以节省内存。然而,在 Swift 中,引用类型分配在堆内存上,而值类型不分配在堆内存上。 维护堆内存是一项重要任务,因为我们的对象足够大,如果它们不被释放,可能会占用内存的大部分。如果应用程序持续内存不足,可能会导致崩溃。 Swift 中的自动引用计数 (ARC)Swift 使用一种更智能的内存管理方式,即自动引用计数 (ARC)。当我们不再需要类实例时,它会自动释放它们占用的内存。然而,正如我们在本文前面所提到的,自动引用计数 (ARC) 仅适用于类实例。结构体和枚举在 Swift 中是值类型,因此它们的实例不会被计数。 ARC 在我们的应用程序代码中创建实例时,会分配一块内存来存储有关该实例的信息。这些信息包含实例的类型,包括与该实例关联的任何存储属性的值。 当实例不再需要时,ARC 也会释放内存。在这种情况下,该实例使用的内存将被释放并用于其他目的。这也确保了在不使用实例时,它们不会占用不必要的内存空间。 但是,如果 ARC 释放了一个仍在使用的实例,我们将无法检索它的任何属性以及与之关联的方法。尽管如此,如果我们尝试这样做,应用程序将会崩溃。 我们还需要确保实例在使用时不会被释放。为此,ARC 会跟踪当前引用每个类实例的属性、常量和变量的数量。只要对该实例至少存在一个活动的引用,ARC 就不会释放该实例。为了实现这一点,每当实例被属性、常量或变量引用时,它就会创建一个对该实例的强引用。这种引用称为强引用,因为它只要强引用仍然存在,就不会导致该实例被释放。 带示例的自动引用计数在这里,我们将通过一个例子来讨论 ARC 的工作原理。让我们创建一个名为 Employee 的简单类,它定义一个名为 Name 的存储常量属性。 Employee 类包含一个初始化器,用于将 name 属性初始化为某个值。它还会打印一条消息,表明实例正在被初始化。然而,Employee 类还包含一个反初始化器,该反初始化器通过打印一条消息来通知用户。 让我们定义三个 Employee 类型的变量,它们将为我们创建的新 Employee 实例设置多个引用。 我们可以创建一个新的 Employee 实例来为其分配我们迄今为止创建的其中一个引用。 这里,我们必须注意到,当我们初始化 Employee 类时,消息会被打印出来。变量 ref1 持有对 Employee 实例的强引用;ARC 确保 Employee 实例保留在内存中。 但是,如果我们为另外两个引用变量分配同一个 Employee,我们将获得另外两个指向 Employee 实例的强引用。 但是,如果我们为我们刚刚为 Employee 实例创建的三个强引用分配 nil,ARC 将会释放它,这意味着我们不再使用该 Employee 实例。 这里,我们必须注意到,在实例被反初始化时,消息会打印在控制台上。 类实例之间的强引用循环在上面的例子中,ARC 会跟踪对 Employee 实例的引用数量。然而,可能会出现一种情况,即类实例的强引用数永远不会降至零。当两个类实例相互持有强引用时会发生这种情况。这种情况称为强引用循环,其中每个实例都使另一个实例保持活跃。 让我们创建一个这样的例子,其中有两个实例之间存在强引用。 在上面的代码中,Employee 实例包含一个 name 属性和一个 Department 实例,该实例最初为 nil。这里,dep(部门)属性为 nil,因为 Employee 可能不总是有部门。 另一方面,Department 实例有一个 name 属性和一个 Employee 数组,代表在该部门工作的员工。employees 属性是可选的,因为部门有可能没有员工。 这两个类都定义了初始化器和反初始化器,以告知用户实例是否正在被初始化或反初始化。现在,让我们定义两个可选类型的变量 Mike 和 IT,它们的类型分别为 Employee 和 Department。 现在,让我们创建一个特定的 Employee 实例和 Department 实例。 我们需要将这两个实例链接起来,以便 Employee 拥有 Department,而 Department 拥有 Employees。 现在,链接这两个实例会在它们之间创建一个强引用循环。Employee 实例对 Department 引用具有强引用,同时 Department 引用对 Employee 引用也具有强引用。如果我们反初始化引用(mike 和 IT),我们会注意到引用计数不会降至零。 我们必须注意到,当我们把这些变量设置为 nil 时,反初始化器没有被调用。然而,由于引用循环阻止了实例被释放,这会导致应用程序内存泄漏。 解决强引用循环有两种方法可以解决强引用循环:弱引用和无主引用。弱引用和无主引用允许我们引用另一个实例而不持有强引用。当另一个实例的生命周期较短时,我们可以使用弱引用。另一方面,当另一个实例具有相同的生命周期或更长的生命周期时,我们可以使用无主引用。 弱引用弱引用被定义为不保持对其所引用实例的强引用的引用。它允许 ARC 在发生强引用循环时释放实例。它不会让引用成为强引用循环的一部分。我们可以使用 weak 关键字将变量声明为弱引用。 正如我们已经讨论过的,弱引用不会对其所引用的实例保持强引用;ARC 可以在其引用的实例被释放时将弱引用设置为 nil。 这里,我们必须确保弱引用总是被声明为变量而不是常量。弱引用也必须被声明为可选类型。 现在,让我们修改 Employee 实例并将 dep(部门)属性声明为 weak。 现在,让我们像下面一样再次创建 Department 实例。 让我们创建两个强引用以及实例之间的链接。 Employee 对 Department 实例持有弱引用,这使我们能够通过将 mike 和 IT 引用变量设置为 nil 来打破强引用。 我们必须注意到,两个实例的反初始化器都会被调用,并且消息会被打印出来。 控制台上会打印出以下输出。 "Mike gets initialized" "IT gets initialized" "IT gets deinitialized" "Mike gets deinitialized" 无主引用无主引用不像弱引用那样保持对所引用实例的强引用。它像弱引用一样引用。然而,当另一个实例具有相同的生命周期或更长的生命周期时,会使用无主引用。我们通过在变量前使用 unowned 关键字来声明一个变量为 unowned。 无主引用始终具有值;因此,声明无主变量不会使变量成为可选。ARC 不会将无主引用变量的值设置为 nil。但是,我们需要确保该引用指向一个未被释放的实例,因为如果我们尝试访问一个实例在被释放后其无主引用的值,我们会得到一个运行时错误。 让我们考虑以下示例,其中我们定义了 Employee 和 Department 之间的关系。Department 可能有也可能没有员工,但 Employee 总是与一个 Department 相关联。在这里,Employee 实例的生命周期永远不会超过它所引用的 Department。这在 Department 拥有一个可选的 Employee 属性,而 Employee 拥有一个无主的 Department 属性的情况下得到了体现。 让我们为 Department 类创建一个可选的引用变量。 现在创建一个 Department 实例,并将一个新的 Employee 实例作为 Department 的 employee 属性。 这里,我们必须注意到,Department 实例对 Employee 实例持有强引用,而 Employee 实例对 Department 实例持有无主引用。由于无主引用,如果我们断开“it”变量持有的强引用,Department 实例的强引用将变为零。 通过反初始化 Department 实例,我们必须注意到 Employee 的 deinit() 将被调用。 输出 "IT Department Deinitialized" "John employee is being deinitialized" 无主可选引用我们可以将无主变量声明为可选。但是,当我们使用无主可选引用时,我们必须保证它始终指向一个有效对象或保持为 nil。让我们考虑以下示例,它扩展了 Department-Employee 关系。在这里,我们有一个不同的实体 Course,它跟踪 Department 提供的课程。 这里,我们必须注意到 Department 对其课程持有强引用。换句话说,我们可以说 Department 拥有它通过对象数组维护的所有课程。然而,Course 不拥有任何 Department,或者它对 Department 持有无主引用。在这里,每个 Course 都是某个 Department 的一部分。因此,department 属性不是可选的,尽管可能存在也可能不存在其他课程来跟随每个 Course。因此,otherCourse 属性是可选的。 让我们创建 Department 和 Course 的实例来看看其功能。 这里,我们创建了一个 Department 实例并为其分配了三门课程。Swift 和 Cocoa 课程各自的 otherCourse 属性被分配了各自的 otherCourse。正如我们已经讨论过的,无主变量不保持对其所持引用的强引用;然而,在这种情况下,无主可选引用变量可能为 nil。这里,我们必须确保 otherCourse 属性始终指向一个未被释放的 Course。 下一个主题Swift iOS 中的面向协议编程 |
我们请求您订阅我们的新闻通讯以获取最新更新。