跳到主要内容

Swift中的强引用与弱引用

提示
  1. 强引用和弱引用的区别:在Swift中,强引用阻止ARC(自动引用计数)回收实例,而弱引用则不会,弱引用使用weak关键字声明。
  2. 引用计数的工作原理:创建类的实例时增加引用计数,当引用设为nil时减少引用计数,实例只有在引用计数为0时才会被ARC回收。
  3. 弱引用的应用:使用弱引用避免循环引用和内存泄漏,弱引用对于解耦组件和防止内存泄漏尤为重要。

在 Swift 中,ARC(自动引用计数)自动处理内存的分配和回收。

然而,我们可以通过指定引用的类型来防止 ARC 自动回收内存。例如,强引用牢牢地持有实例,并不允许 ARC 回收。

同样,弱引用不能阻止 ARC 回收实例。

在了解强引用和弱引用之前,请确保你理解了 Swift 中类和对象是如何工作的

注意:属性的声明默认是强引用。要声明弱引用,我们使用 weak 关键字。

Swift 中的强引用

在 Swift 中,每当我们创建一个类的实例时,引用计数值会从 0 增加到 1。同样,如果我们回收实例,计数将减少到 0。让我们看一个例子,

class Employee {
var name: String
var colleague: Employee?

// 定义初始化器
init(name : String) {
self.name = name;
}
}

// 创建两个 Employee 的对象
var sabby: Employee? = Employee(name: "Sabby")
var cathy: Employee? = Employee(name: "Cathy")

在上面的示例中,我们创建了 Employee 类的实例:sabby 和 cathy。现在 sabby 和 cathy 的引用计数都是 1

这里,我们在类中创建了一个强引用。

var colleague: Employee?

现在,让我们使用这个引用将 sabbycolleague 属性引用到 cathy

sabby?.colleague = cathy

在这种情况下,为 cathy 建立了一个新的引用,将 cathy 的引用计数从 1 增加到 2

同样,如果我们将 sabby 指定给 cathy 的同事,sabby 的引用计数将增加到 2

cathy?.colleague = sabby

这里,sabby 和 cathy 的实例都将有引用计数 2

回收实例

在 Swift 中,只有在引用计数为 0 时,内存实例才会被回收。要手动回收内存实例,我们将引用赋值为 nil。例如,

sabby = nil
cathy = nil

当我们将实例赋值为 nil 时,实例的引用计数将减少 1。这里,上面的代码将 sabbycathy 的引用计数减少 1

然而,由于强引用的存在,sabbycathy 的引用计数为 2,所以最终回收后的引用计数将是 12 - 1)。

这就是强引用的 sabbycathy 不会被回收(引用计数不等于 0)的原因。

注意:类类型必须是可选类型,以便我们可以将该类的对象赋值为 nil。这就是为什么我们使用了 Employee? 而不是 Employee

示例:Swift 强引用

// 声明一个类
class Employee {

var name: String
var salary: Int
var colleague: Employee?

// 定义初始化器
init(name: String, salary: Int) {
self.name = name
self.salary = salary
}

// 定义析构器
deinit {
print("内存已回收")
}
}

// 创建 Employee 的实例
var sabby: Employee? = Employee(name: "Sabby", salary: 50000)
var cathy: Employee? = Employee(name: "Cathy", salary: 45000)

// 增加 cathy 和 sabby 的引用计数到 2
sabby?.colleague = cathy
cathy?.colleague = sabby

// deallocate objects
sabby = nil
cathy = nil
```

在上述示例中,我们将 `nil` 赋值给了 `sabby` 和 `cathy` 的实例。然而,`sabby` 和 `cathy` 是强引用,释放它们只会将它们的引用计数从 **2** 减少到 **1**

这就是为什么析构器

```swift
deinit {
print("内存已释放")
}

没有被调用,因此我们没有得到任何输出。

如果我们想要完全释放实例的内存,我们可以使用弱引用。

Swift 弱引用

正如之前提到的,弱引用不会阻止对象被释放。这是因为当我们将一个属性声明为弱引用时,该属性的引用计数永远不会超过 1。

class Employee {
weak var colleague: Employee?
...
}

这里,我们使用了 weak 关键字来表示 colleague 是一个弱属性。

注意:默认情况下,属性是强类型的。

示例 2:弱引用

// 声明一个类
class Employee {

var name: String
var salary: Int

// 弱属性声明
weak var colleague: Employee?

// 定义初始化器
init(name: String, salary: Int) {
self.name = name
self.salary = salary
}

// 定义析构函数
deinit {
print("内存已释放")
}
}

// 创建 Employee 的实例
var sabby: Employee? = Employee(name: "Sabby", salary: 50000)
var cathy: Employee? = Employee(name: "Cathy", salary: 45000)

// sabby 实例的 colleague 属性引用 cathy 实例
sabby?.colleague = cathy

// cathy 实例的 colleague 属性引用 sabby 实例
cathy?.colleague = sabby

// 释放对象
sabby = nil
cathy = nil

输出

内存已释放
内存已释放

在上面的示例中,我们创建了 Employee 类的实例:sabby 和 cathy。

var sabby: Employee? = Employee(name: "Sabby", salary: 50000)
var cathy: Employee? = Employee(name: "Cathy", salary: 45000)

现在 sabby 和 cathy 的引用计数都是 1

这里,我们将 sabby 的 colleague 属性引用到了 cathy,反之亦然。

sabby?.colleague = cathy

cathy?.colleague = sabby

在这种情况下,由于这次我们使用了 colleague 属性的弱引用,sabbycathy 的引用计数保持为 1

这就是为什么当我们释放实例时。

sabby = nil
cathy = nil

引用被完全释放,析构函数被调用。

deinit {
print("内存已释放")
}

因此,我们得到了输出 "内存已释放"。