本文迁移自 SwiftGG 中文版《The Swift Programming Language 6.2.3》的“析构过程”,原文由 Apple Inc.、Swift 项目作者及 SwiftGG 译者贡献,依据 Apache License 2.0(含 Runtime Library Exception)发布。本站仅做 Markdown、链接与版式适配。
释放需要自定义清理的资源。
析构器仅适用于类类型,析构器会在类实例被释放之前立即调用。使用 deinit 关键字来编写析构器,类似于使用 init 关键字编写构造器。
析构过程原理
Swift 会在实例不再需要时自动释放它们,以释放资源。Swift 通过自动引用计数(ARC)来管理实例的内存,如 自动引用计数 中所述。通常,在实例被释放时不需要执行手动清理。然而,当你处理自己的资源时,可能需要进行一些额外的清理。例如,如果创建一个自定义类来打开文件并向其中写入数据,可能需要在类实例被释放之前关闭文件。
类定义中每个类最多只能有一个析构器。析构器不接受任何参数,并且是没有括号的:
deinit {
// 执行析构过程
}析构器会在实例释放之前自动调用。不能自行调用析构器。父类的析构器会被子类继承,并且在子类析构器实现的末尾被自动调用。即使子类没有提供自己的析构器,父类的析构器也总是会被调用。
由于实例在析构器调用完成后才会被释放,因此析构器可以访问该实例的所有属性,并可以根据这些属性修改其行为(例如查找需要关闭的文件的名称)。
析构器实践
下面是一个析构器实践的例子。这个例子定义了两个新类型,Bank 和 Player,用于一个简单的游戏。Bank 类管理一种虚构的货币,这种货币的流通量永远不会超过 10,000 个金币。游戏中只能有一个 Bank,因此 Bank 被实现为一个类,使用类型属性和类型方法来存储和管理其当前状态:
class Bank {
static var coinsInBank = 10_000
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}Bank 通过其 coinsInBank 属性跟踪当前持有的金币数量。它还提供了两个方法——distribute(coins:) 和 receive(coins:)——来处理金币的分发和接收。
distribute(coins:) 方法在分发金币之前检查银行中是否有足够的金币。如果金币不足,Bank 会返回一个比请求的数量更小的数字(如果银行中没有金币,则返回零)。该方法返回一个整型值,表示实际提供的金币数量。
receive(coins:) 方法只是将接收到的金币数量重新添加到银行的金币库中。
Player 类描述了游戏中的一个玩家。每个玩家的钱包中随时都会存储一定数量的金币,由 coinsInPurse 属性表示:
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
deinit {
Bank.receive(coins: coinsInPurse)
}
}每个 Player 实例在初始化时都会从银行获得指定的金币作为初始津贴,但是如果可用金币不足,Player 实例获得的金币数量可能会少于指定数量。
Player 类定义了一个 win(coins:) 方法,该方法从银行获取一定数量的金币并将其添加到玩家的钱包中。Player 类还实现了一个析构器,该析构器在 Player 实例被释放之前调用。在这里,析构器只是将玩家的所有金币返还给银行:
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// 打印 "A new player has joined the game with 100 coins"。
print("There are now \(Bank.coinsInBank) coins left in the bank")
// 打印 "There are now 9900 coins left in the bank"。创建了一个新的 Player 实例的时候,会向 Bank 请求获取 100 个金币(如果有的话)。这个 Player 实例被存储在一个名为 playerOne 的可选类型变量中。这里使用可选变量是因为玩家可以在任何时候退出游戏。通过可选类型可以跟踪当前是否有玩家在游戏中。
由于 playerOne 是一个可选类型,因此在访问其 coinsInPurse 属性以打印默认金币数量时,以及每次调用其 win(coins:) 方法时,都需要使用感叹号(!)来强制解包:
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// 打印 "PlayerOne won 2000 coins & now has 2100 coins"。
print("The bank now only has \(Bank.coinsInBank) coins left")
// 打印 "The bank now only has 7900 coins left"。在这里,玩家赢得了 2,000 个金币。玩家的钱包现在有 2,100 个金币,而银行只剩下 7,900 个金币。
playerOne = nil
print("PlayerOne has left the game")
// 打印 "PlayerOne has left the game"。
print("The bank now has \(Bank.coinsInBank) coins")
// 打印 "The bank now has 10000 coins"。玩家现在已经退出游戏。这是通过将可选类型变量 playerOne 设置为 nil 来表示的,意味着“没有 Player 实例”。在这一刻,playerOne 变量对 Player 实例的引用被断开。没有其他属性或变量仍然引用该 Player 实例,因此它会被释放以回收其内存。在此之前,它的析构器会被自动调用,并将其金币返还给银行。