析构(deinitialization
)是一个对类的实例释放的一个过程,在析构的过程中我们会调用析构器(deinitializer
)来参与类实例的释放,析构器只有在某个类的实例被释放前才会被调用。我们用deinit关键字介绍并引出这个这个析构器,这一点和构造过程中调用的构造器的用法写法是一样的。并且析构器只能在类的实例中使用。
swift可以对不在需要的实例进行释放,释放不在使用的资源,通过自动引用计数(Automatic Reference Counting
)对实例的内存进行管理。总的来说,我们不需要手动做实例释放的清理工作。这个自动引用计数将会在后面的章节中详细介绍。
如果我们要使用我们自己的资源的时候,可能要手动执行某些附加的清理工作。举个例子,如果创建来一个自定义的类,这个类要打开一个文件并且要做一些数据写入的工作,这个时候我们可能要在这个类的实例被释放前关闭这个已打开的文件。
类的定义里面最多且只能有一个析构器。而且这个析构器不能带有任何参数的。
deinit {
// perform the deinitialization
}
析构器的调用是自动的,它在类的实例被执行释放之前。所以我们不用去刻意的调用某个类的析构器。除非这个析构器是我们为某个类要特殊执行某个任务而自定义的析构器。子类会继承父类的析构器,并且父类的析构器会在子类最后的析构器的实现里面会被调用。即便是子类里面并备有提供析构器,父类的析构器总是会最后被调用的。
最后呢,因为实例的析构器被调用后,实例才会被完全释放(清理-内存管理)。所以说,一个析构器可以访问读取实例的所有属性,并且可以基于这些属性析构器还可以修改它的行文(查找一个需要被关闭的文件)。
下面是实践中的析构器的案例,定义来两个新的类型(Bank和Player的类),class Bank
是一个管理虚拟(made-up)的硬币,这个货币的在总量上也绝不会超过10,000个。在这个游戏里面只有一个类Bank,所以呢Bank作为一个类在实现的过程中,用类型属性(type property
:define value that are universal to the type ) 和类型方法(type method
:define methods that are called on the type itself ) 来管理和储存这些硬币的状态。
下面定义的类Bank要随时跟踪一个类型属性coinsInBank
里面现有硬币的数量。和两个类型方法distribute(coins: )
和receive(coins: )
来管理现有硬币的分配与收集。其中这个distribute(coins: )类型方法会检查要分配的硬币(numberOfCoinsRequested
)和银行现有的硬币(coinsInBank
),如果说没用足够的硬币,那么这时候类Bank就回返回一个比需要分配的数量还要少的数量。receive(coins: )这个方法是用来收集硬币来添加到coinsInBank属性里面的。
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
}
}
这个类Player描述的是游戏的参与者玩家,每一个玩家会有一定数量的硬币存储在钱包里面。该类展示了这个玩家的钱包里的硬币(coinInPurse
)属性。
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)
}
}
每一个刚加入游戏的玩家都会在构造过程中得到从银行获取的100个硬币。如果银行里面的硬币不够100,新加入的玩家可能会获得少于100个硬币。上面的Player类定义了一个win(coins: )的方法。这个方法是用来检索从银行获取的钱添加到玩家的钱包。同样的这个类还定义了一个析构器,这个析构器是在玩家实例被释放之前被调用的,也就是说实例先要被释放,然后才能执行这个析构器,析构器将会把玩家所拥有的硬币返回到银行里面去。
var playerOne: Player? = Player(coins: 100)
// 新加进来的玩家拥有100个硬币 银行里面就会剩余9900个硬币了
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
print("There are now \(Bank.coinsInBank) coins left in the bank")
创建一个新的玩家实例并且该玩家会从银行获取100个硬币。这个玩家的实例会被存储在一个为Player?
的playerone里面。在这里使用可选类型的原因是 因为该玩家可能会在任何时候离开这个游戏,这个可选类型让我们跟踪监控现在在游戏里面的玩家。
因为这个PlayerOne是一个类型类型,所以呢在访问玩家的coinsInPurse属性的时候就要对这个可选类型进行展开,用!
对这个可选类型进行展开读取里面的数据。在用dot语法调用win(coins: )方法。这个时候就可以读取输出现在玩家所拥有的硬币数量和银行里面剩余的硬币数量。
playerOne!.win(coins: 2_000)
// 玩家拥有2000个硬币的时候 银行里面就剩余了7900个硬币
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
print("The bank now only has \(Bank.coinsInBank) coins left")
// 玩家离开了游戏 所以银行里面硬币的总数又回到了10000个了。
playerOne = nil
print("PlayerOne has left the game")
print("The bank now has \(Bank.coinsInBank) coins")
当代码运行带playerOne = nil
这里的时候 前面的所有实例都会被重新设置为nil
了。这就意味着现在PlayerOne变量的引用已经断开了,再也没有任何一个Player的实例链接该变量属性了,所以这样这个类的实例就会被释放了,自动清理了内存,析构器在最后会被自动执行 所有的硬币会呗返还给银行。