Dans le langage Swift, une méthode est une unité de code qui accomplit une tâche spécifique. Les méthodes sont des fonctions associées à une classe, une structure ou une énumération et peuvent être appelées sur des instances de ces types. Les méthodes peuvent recevoir des arguments en entrée, renvoyer des valeurs en sortie et effectuer des opérations sur les propriétés et les variables associées à l'instance sur laquelle elles sont appelées.

Voici un exemple simple de méthode en Swift :


class MyClass {
    func sayHello() {
        print("Hello!")
    }
}

let myInstance = MyClass()
myInstance.sayHello() 
// affiche "Hello!"

Dans cet exemple, la méthode sayHello() est définie dans la classe MyClass. Elle n'a pas d'arguments en entrée ni de valeurs en sortie, et se contente d'afficher "Hello!" sur la console lorsqu'elle est appelée sur une instance de MyClass.

Méthode d'instance

Les classes, les structures et les énumérations peuvent définir des méthodes d'instance (des fonctions que les instances d'une classe pourront utiliser), qui encapsulent des tâches et des fonctionnalités spécifiques pour travailler avec une instance d'un type donné.

Les méthodes d'instance sont des fonctions qui appartiennent à une classe, une structure ou à une énumération. Les méthodes ont exactement la même syntaxe que les fonctions.

Voici un deuxième exemple qui définit une classe simple appelée Counter, qui peut être utilisée pour compter le nombre de fois qu'une action se produit :


class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

La classe Counter définit trois méthodes d'instance :

  • increment() : incrémente le compteur de 1.
  • increment(by: Int) : incrémente le compteur d'un nombre entier spécifié.
  • reset() : remet le compteur à zéro.

La classe Counter déclare également une propriété de variable, count, pour garder une trace de la valeur actuelle du compteur.

Vous appelez des méthodes avec la même syntaxe de points que les propriétés :


let counter = Counter()
// La valeur initial du compteur est 0

counter.increment()
// La valeur du compteur est maintenant 1

counter.increment(by: 5)
// La valeur du compteur est maintenant 6

counter.reset()
// La valeur du compteur est maintenant 0

Les paramètres de fonction peuvent avoir à la fois un nom (à utiliser dans le corps de la fonction) et une étiquette d'argument ou label (à utiliser lors de l'appel de la fonction), comme décrit dans le chapitre les fonctions. Il en va de même pour les paramètres de méthode, car les méthodes ne sont que des fonctions associées à un type.

La propriété self

Chaque instance a une propriété implicite appelée self, qui est exactement l'équivalente de l'instance elle-même. Vous utilisez la propriété self pour faire référence à l'instance actuelle dans ses propres méthodes.

Nous aurions pu écrire précédemment la méthode increment() comme ceci :


func increment() {
    self.count += 1
}

En pratique, vous n'aurez pas besoin d'écrire self très souvent dans votre code. Si vous n'écrivez pas explicitement self, Swift suppose que vous faites référence à une propriété ou à une méthode de l'instance actuelle chaque fois que vous utilisez une propriété ou un nom de méthode connu dans une méthode. Cette hypothèse est démontrée par l'utilisation de count (plutôt que self.count) dans les trois méthodes d'instance pour Counter.

La principale exception à cette règle se produit lorsqu'un nom de paramètre pour une méthode d'instance porte le même nom qu'une propriété de cette instance. Dans cette situation, le nom du paramètre est prioritaire et il devient nécessaire de faire référence à la propriété de manière plus qualifiée. Vous utilisez la selfpropriété pour faire la distinction entre le nom du paramètre et le nom de la propriété.

Ici, self lève l'ambiguïté entre un paramètre de méthode appelé x et une propriété également appelée x :


struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("Ce point est à droite de la ligne où x == 1.0")
}
// "Ce point est à droite de la ligne où x == 1.0"

Sans le préfixe self, Swift supposerait que les deux utilisations de x faisaient référence au paramètre de méthode appelé x.

Modification des types de valeurs à partir des méthodes d'instances

Les structures et les énumérations sont des types de valeurs. Par défaut, les propriétés d'un type valeur ne peuvent pas être modifiées à partir de ses méthodes d'instances.

Toutefois, si vous devez modifier les propriétés de votre structure ou énumération dans une méthode particulière, vous pouvez activer le comportement de mutation pour cette méthode. La méthode peut alors muter (c'est-à-dire modifier) ses propriétés à partir de la méthode.

Vous pouvez activer ce comportement en plaçant le mot-clé mutating avant le mot-clé func de cette méthode :


struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)

print("Le point est maintenant à (\(somePoint.x), \(somePoint.y))")
// "Le point est maintenant à (3.0, 4.0)"

La structure Point ci-dessus définit une méthode moveBy(x:y:) de mutation, qui déplace une instance Point d'une certaine quantité. Au lieu de renvoyer un nouveau point, cette méthode modifie en fait le point sur lequel elle est appelée. Le mot-clé mutating est ajouté à sa définition pour lui permettre de modifier ses propriétés.

Notez que vous ne pouvez pas appeler une méthode de mutation sur une constante de type structure, car ses propriétés ne peuvent pas être modifiées, même s'il s'agit de propriétés variables.


let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// Erreur

Self et mutation

Les méthodes de mutation peuvent affecter une instance entièrement nouvelle à la propriété implicite self. L'exemple Point ci-dessus aurait pu être écrit de la manière suivante :


struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

Cette version de la méthode de mutation moveBy(x:y:) crée une nouvelle structure dont les valeurs x et y sont définies sur l'emplacement cible. Le résultat final de l'appel de cette version alternative de la méthode sera exactement le même que pour l'appel de la version précédente.

Les méthodes de mutation pour les énumérations peuvent définir le paramètre implicite self sur un cas différent de la même énumération :


enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}

var ovenLight = TriStateSwitch.low

ovenLight.next()
// ovenLight a maintenant pour valeur .high

ovenLight.next()
// ovenLight a maintenant pour valeur .off

Cet exemple définit une énumération pour un commutateur à trois états. Le commutateur alterne entre trois états d'alimentation différents (off, low et high) chaque fois que sa méthode next() est appelée.

Méthodes de types ou Méthodes de classes

Les méthodes d'instances, comme décrites ci-dessus, sont des méthodes que vous appelez sur une instance d'un type particulier. Vous pouvez également définir des méthodes appelées sur le type lui-même. Ces types de méthodes sont appelés méthodes de types ou méthodes de classes.

Vous indiquez les méthodes de types en écrivant le mot-clé static avant le mot-clé func de la méthode. Les classes peuvent utiliser le mot-clé class à la place, pour permettre aux sous-classes de remplacer l'implémentation de cette méthode par la superclasse. Les méthodes de type sont appelées avec une syntaxe à points, comme les méthodes d'instance.


class SomeClass {
    class func someTypeMethod() {
        // Implémentation de la méthode
    }
}
SomeClass.someTypeMethod()

Dans le corps d'une méthode de type, la propriété implicite self fait référence au type lui-même, plutôt qu'à une instance de ce type. Cela signifie que vous pouvez utiliser self pour lever l'ambiguïté entre les propriétés de type et les paramètres de méthode de type, tout comme vous le faites pour les propriétés d'instance et les paramètres de méthode d'instance.

Plus généralement, tous les noms de méthode et de propriété non qualifiés que vous utilisez dans le corps d'une méthode feront référence à d'autres méthodes et propriétés. Une méthode de type peut appeler une autre méthode de type avec le nom de l'autre méthode, sans avoir besoin de la préfixer avec le nom du type. De même, les méthodes de type sur les structures et les énumérations peuvent accéder aux propriétés de type en utilisant le nom de la propriété de type sans préfixe de nom de type.

L'exemple ci-dessous définit une structure appelée LevelTracker, qui suit la progression d'un joueur à travers les différents niveaux ou étapes d'un jeu. C'est un jeu solo, mais il peut stocker des informations pour plusieurs joueurs sur un seul appareil.

Tous les niveaux du jeu (à l'exception du niveau 1) sont verrouillés lors de la première partie. Chaque fois qu'un joueur termine un niveau, ce niveau est déverrouillé pour tous les joueurs sur l'appareil. La structure LevelTracker utilise des propriétés de type et des méthodes pour garder une trace des niveaux du jeu qui ont été déverrouillés. Il suit également le niveau actuel d'un joueur individuel.


struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

La structure LevelTracker garde une trace du niveau le plus élevé que n'importe quel joueur a débloqué. Cette valeur est stockée dans une propriété de type appelée highestUnlockedLevel.

LevelTracker définit également deux fonctions de type pour travailler avec la propriété highestUnlockedLevel. La première est une fonction de type appelée unlock(_:), qui met à jour la valeur de highestUnlockedLevel chaque fois qu'un nouveau niveau est déverrouillé. La seconde est une fonction de type pratique appelée isUnlocked(_:), qui renvoie true si un numéro de niveau particulier est déjà déverrouillé. (Notez que ces méthodes de type peuvent accéder à la propriété highestUnlockedLevel de type sans que vous ayez besoin de l'écrire en tant que LevelTracker.highestUnlockedLevel.)

En plus de sa propriété de type et de ses méthodes de type, LevelTracker suit la progression d'un joueur individuel dans le jeu. Il utilise une propriété d'instance appelée currentLevel pour suivre le niveau auquel un joueur joue actuellement.

Pour faciliter la gestion de la propriété currentLevel, LevelTracker définit une méthode d'instance appelée advance(to:). Avant la mise à jour de currentLevel, cette méthode vérifie si le nouveau niveau demandé est déjà déverrouillé. La méthode advance(to:) renvoie une valeur booléenne pour indiquer si elle a pu ou non définir currentLevel.

La structure LevelTracker est utilisée avec la classe Player, illustrée ci-dessous, pour suivre et mettre à jour la progression d'un joueur individuel :


class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

La classe Player crée une nouvelle instance de LevelTracker pour suivre la progression de ce joueur. Il fournit également une méthode appelée complete(level:), qui est appelée chaque fois qu'un joueur termine un niveau particulier. Cette méthode déverrouille le niveau suivant pour tous les joueurs et met à jour la progression du joueur pour le faire passer au niveau suivant.

Vous pouvez créer une instance de la classe Player pour un nouveau joueur et voir ce qui se passe lorsque le joueur termine le niveau 1 :


var player = Player(name: "Louis")
player.complete(level: 1)
print("Le niveau débloqué le plus élevé est maintenant le 2 \(LevelTracker.highestUnlockedLevel)")
// "Le niveau débloqué le plus élevé est maintenant le 2"

Si vous créez un deuxième joueur, que vous essayez de déplacer vers un niveau qui n'est encore débloqué par aucun joueur du jeu, la tentative de définir le niveau actuel du joueur échoue :


player = Player(name: "Maxime")
if player.tracker.advance(to: 6) {
    print("Le joueur est maintenant au niveau 6")
} else {
    print("Le niveau 6 n'a pas été débloqué")
}
// "Le niveau 6 n'a pas été débloqué"