Découverte de Swift (13)

Après un long chapitre, et trois billets, sur l'initialisation, ma découverte de Swift se poursuit avec un chapitre plus court sur la désinitialisation.

Un désinitialisateur est appelé lorsque l'instance d'une classe est effacée de l'espace-mémoire.

Il se déclare avec le mot-clé deinit, de la même façon que le mot-clé init permet de déclarer un initialisateur.

Les désinitialisateurs ne sont disponibles que pour les classes, il n'en pas possible d'en définir pour une structure, une énumération ou tout autre type.

Comment fonctionne la désinitialisation ?

Swift libère automatiquement l'espace-mémoire des instances qui ne sont plus nécessaires. Dans la plupart des cas, il n'est pas nécessaire de coder d'opérations spécifiques pour cela. Il existe cependant des cas où un travail spécifique de nettoyage est nécessaire, par exemple si une classe utilise ses propres ressources.

Un exemple concret : si une classe ouvre un fichier et y écrit des données, il peut être nécessaire de fermer le fichier correctement avant qu'une instance de cette classe soit détruite.

Chaque classe peut déclarer un désinitialisateur, et seulement un. Il est appelé automatiquement juste avant la destruction de l'instance. Il n'est pas possible d'appeler explicitement le désinitialisateur d'une classe : cela permet notamment d'accéder aux propriétés de l'instance.

Dans le cadre de l'héritage, le désinitialisateur de la classe-mère est hérité dans sa classe-fille, et il est appelé automatiquement à la fin du désinitialisateur de la classe-fille. Il est également appelé automatiquement si la classe-fille ne déclare pas un nouveau désinitialisateur spécifique.

Désinitialisateur en action

Voyons cela avec un exemple concret.

Commençons par définir une classe Bank pour modéliser le fonctionnement de la banque d'un jeu comme le Monopoly :

class Bank {
    
    static var coinsInBank = 10_000
    
    static func receive(coins: Int) {
        coinsInBank += coins
    }
    
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberofCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        coinsInBank -= numberofCoinsToVend
        return numberofCoinsToVend
    }
    
}

Toutes les propriétés et méthodes de cette classe sont définies comme statiques car la banque est unique dans le jeu. On y trouve : – une propriété coinsInBank initialisée à 10.000 et qui gère le montant en banque – une méthode receive pour ajouter un certain montant à la banque – une méthode distribute pour distribuer de l'argent de la banque, à concurrence du montant actuellement en banque

Créons maintenant une classe Player pour représenter un joueur :

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)
    }
}

La classe Playerdispose des propriétés et méthodes suivantes : – une propriété stockée coinsInPursepour gérer l'argent dont dispose le joueur – un initialisateur init(coins: Int) pour créer un joueur avec un montant initial – une méthode win(coins : Int) pour recevoir un certain montant de la banque (plafonné par le montant actuellement en banque) – un désinitialisateur deinit() qui remet à la banque l'argent du joueur lorsqu'il est supprimé

Commençons à joueur avec nos 2 classes :

var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// Prints "A new player has joined the game with 100 coins"
print("There is now \(Bank.coinsInBank) left in the bank")
// Prints "There is now 9900 left in the bank"

Nous créons un joueur avec 100 pièces. Nous utilisons pour cela un optional car un joueur peut quitter la partie à tout moment. La création d'un joueur avec 100 pièces a déduit d'autant le montant de la banque.

On peut ensuite continuer à jouer en faisant gagner 2.000 pièces à notre joueur :

playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins and now has \(playerOne!.coinsInPurse) coins")
// Prints "PlayerOne won 2000 coins and now has 2100 coins"
print("There is now \(Bank.coinsInBank) left in the bank")
// Prints "There is now 7900 left in the bank"

Et si notre joueur quittait la partie ? Voyons comment cela se passe :

playerOne = nil
print("PlayerOne has left the game")
print("There is now \(Bank.coinsInBank) left in the bank")
// Prints "There is now 10000 left in the bank"

En affectant nil à playerOne, nous avons de fait détruit cette instance de Player puisqu'il s'agissait de la seule référence à cette instance. Le désinitialisateur a donc été appelé et a remis à la banque les pièces que possédait le joueur : la banque a donc récupéré les 2100 pièces et en possède à nouveau 10000.

Et la suite ?

Comme promis, il s'agissait d'un chapitre court pour aborder un sujet plus simple que le précédent. Le prochain sera peut-être un peu plus long mais j'espère néanmoins le faire tenir dans un seul billet. Il sera consacré à l'optional chaining.

#swift

Zéro Tech, le blog Tech de Zéro Janvier@zerojanvier@mamot.fr