Have you ever wondered what happens to your class instances when they are no longer needed? How do you free up the resources they occupy and perform any custom cleanup? In this article, you will learn everything you need to know about deinitializers in Swift, from the basics to the advanced topics.
What are deinitializers?
Deinitializers are special methods that are called automatically just before a class instance is deallocated. They allow you to release any resources that require custom cleanup, such as files, sockets, timers, etc.
Deinitializers are written with the `deinit` keyword, similar to how initializers are written with the `init` keyword. They don't take any parameters and are written without parentheses:
deinit {
// perform the deinitialization
}
You can only use deinitializers with classes, not with structs or enums. Each class can have at most one deinitializer per class. You don’t need to call a deinitializer yourself; Swift will do it for you when the instance is no longer needed.
How deinitialization works
Swift handles the memory management of instances through automatic reference counting (ARC), which tracks how many references exist to each instance. When the reference count of an instance drops to zero, it means that no one is using it anymore, and Swift can deallocate it and free up its memory.
However, before deallocating an instance, Swift will call its deinitializer (if it has one) to give it a chance to perform any custom cleanup. For example, if you have a class that opens a file and writes some data to it, you might want to close the file in the deinitializer to avoid leaving it open.
Deinitializers are also inherited by subclasses from their superclasses. If a subclass has its own deinitializer, it will be called first, followed by the superclass deinitializer. This ensures that the subclass properties are cleaned up before the superclass properties.
Deinitializers in action
Let’s see an example of how to use deinitializers in Swift. We’ll define two classes: Bank
and Player
. The Bank
class manages a made-up currency that can never have more than 10,000 coins in circulation. There can only be one Bank
in the game, so we’ll use type properties and methods to store and manage its state:
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
}
}
The Player
class represents a player in the game who can receive and return coins from the bank. Each player has a certain number of coins stored in their purse:
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse \= Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
}
Now we can create some players and see how many coins they have:
var playerOne: Player? \= Player(coins: 100)
print("PlayerOne has \(playerOne!.coinsInPurse) coins")
// Prints "PlayerOne has 100 coins"
var playerTwo: Player? \= Player(coins: 200)
print("PlayerTwo has \(playerTwo!.coinsInPurse) coins")
// Prints "PlayerTwo has 200 coins"
We can also see how many coins are left in the bank:
print("The bank has \(Bank.coinsInBank) coins left")
// Prints "The bank has 9700 coins left"
Now suppose that player one wins some coins from player two:
playerOne?.win(coins: 50)
print("PlayerOne now has \(playerOne!.coinsInPurse) coins")
// Prints "PlayerOne now has 150 coins"
print("PlayerTwo now has \(playerTwo!.coinsInPurse) coins")
// Prints "PlayerTwo now has 150 coins"
The bank’s coin count remains unchanged:
print("The bank still has \(Bank.coinsInBank ) coins left")
// Prints "The bank still has 9700 coins left"
What happens if player one decides to leave the game? We can set their variable to nil
to break the reference to their instance:
playerOne \= nil
This will cause the player one instance to be deallocated, and its coins will be returned to the bank. But how do we implement this behavior? This is where we need a deinitializer. We can add a deinitializer to the Player
class that calls the Bank.receive(coins:)
method when the player leaves:
deinit {
Bank.receive(coins: coinsInPurse)
}
Now when we set player one to nil
, we’ll see that the bank’s coin count increases:
playerOne \= nil
print("The bank now has \(Bank.coinsInBank) coins left")
// Prints "The bank now has 9850 coins left"
And that’s how deinitializers work in Swift!
Conclusion
In this article, you learned how to use deinitializers in Swift to perform custom cleanup before a class instance is deallocated. You learned that deinitializers are written with the deinit
keyword, that they are called automatically by Swift, and that they are inherited by subclasses from superclasses. You also saw an example of how to use deinitializers in a simple game scenario.
Deinitializers are a powerful feature of Swift that allow you to manage your resources efficiently and avoid memory leaks. They are especially useful when you work with external resources that need to be closed or released when you’re done with them.