Module 2 - Note de cours

← Retour au module principal

Playground

Le playground est un outil de développement intégré à Xcode qui permet d'écrire du code Swift et de voir les résultats en temps réel. Il est très utile pour tester des algorithmes ou des idées rapidement.

Créer un projet playground

Pour ouvrir un playground, il suffit de cliquer sur "File" dans la barre de menu, puis "New" et enfin "Playground". Il est possible de choisir entre un playground vide ou un playground interactif.

Vidéo démonstrative

Utilisation du playground

Introduction au langage Swift (Partie 1)

Référence officielle

Swift est un langage de programmation moderne, développé par Apple et présenté en 2014 lors de la WWDC (Worldwide Developers Conference). Créé pour remplacer Objective-C, il est puissant, facile à apprendre et inspiré de nombreux langages comme Python, Ruby et Rust. Swift est open source, ce qui le rend accessible à tous, et constitue la base idéale pour développer des applications dans l’écosystème Apple.

Les commentaires

Swift propose trois types de commentaires :


    // Ceci est un commentaire classique
    
    //: Ceci est un commentaire Swift supportant le markup
    
    /* Ceci 
            est un 
                    commentaire 
                                multiligne */
  

Variables, constantes et types

Swift peut inférer automatiquement le type des variables et des constantes, mais il est également possible de l’indiquer explicitement :


    // Variable
    var nombre = 10       // Inférence : Swift déduit que 'nombre' est un Int
    var autreNombre: Int  // Annotation explicite
    autreNombre = 20

    // Constante
    let constante = 30    // Inférence : Swift déduit que 'constante' est un Int
    let autreConstante: Int = 40 // Annotation explicite

    autreConstante = 50 // Erreur : impossible de modifier une constante
  

Vous pouvez déclarer plusieurs constantes ou variables sur une seule ligne, en les séparant par des virgules


    var x = 0.0, y = 0.0, z = 0.0
    let x = 0.0, y = 0.0, z = 0.0
  

Les noms de variables et de constantes peuvent contenir presque tous les caractères, y compris les caractères Unicode :


    let π = 3.14159
    let 你好 = "你好世界"
    let 🐶🐮 = "dogcow"
    let bébé = "👶🏽"
  

L'interpolation

L'interpolation permet d'insérer des variables ou des constantes dans une chaîne de caractères :


    let prenom = "Mathieu"
    let age = 20
    var message = "Bonjour, je m'appelle \(prenom) et j'ai \(age) ans."
    print(message) // Affiche : Bonjour, je m'appelle Mathieu et j'ai 20 ans.
  

Les types de données

Swift propose plusieurs types de données :


    var numero: Int     // Nombres entiers, positifs ou négatifs
    var uNumero: UInt   // Nombres entiers, positifs uniquement
    var booleen: Bool   // True ou false
    var chaine: String  // Texte
    var double: Double  // Nombres décimaux avec une précision double
    var flottant: Float // Nombres décimaux avec une précision simple
  

Structures conditionnelles

Swift supporte des structures conditionnelles comme if, else if, else, switch, et l'opérateur ternaire :


    var x = 7

    // Condition avec if
    if x > 5 {
        print("x est supérieur à 5")
    }

    // Condition avec if et else
    if x > 10 {
        print("x est supérieur à 10")
    } else {
        print("x est inférieur ou égal à 10")
    }

    // Condition avec if, else if et else
    if x == 0 {
        print("x est nul")
    } else if x < 0 {
        print("x est négatif")
    } else {
        print("x est positif")
    }

    // 

    // Opérateur ternaire
    var message = x > 0 ? "Positif" : "Négatif"

    // Switch case
    switch x {
    case 1, 2, 3:
        print("Petit nombre")
    case 7:
        print("Nombre premier")
    default:
        print("Autre")
    }
  

Les boucles

Swift propose plusieurs types de boucles :


    // Boucle for
    for i in 0...5 {
        print(i) // Affiche les nombres de 0 à 5
    }
    
    for i in 0..<5 {
        print(i) // Affiche les nombres de 0 à 4
    }

    // le underscore indique au compilateur de ne pas se preoccuper de l'affectation
    for _ in 0..<5 {
        print("Hello") // Affiche "Hello" 5 fois
    }

    // Boucle while
    var i = 0
    while i < 5 {
        print(i)
        i += 1
    }

    // Boucle repeat-while (do-while)
    var j = 0
    repeat {
        print(j)
        j += 1
    } while j < 5
  

Les fonctions

Fonction sans paramètre

Une fonction sans paramètre est une fonction qui ne prend rien en entrée pour effectuer son travail. Elle exécute toujours la même action, sans avoir besoin d’informations supplémentaires.


    func direBonjour() {
        print("Bonjour !")
    }

    direBonjour() // Affiche : Bonjour !
    

Fonction avec paramètres

Une fonction avec paramètres est une fonction qui prend des informations en entrée pour effectuer son travail. Ces informations, qu’on appelle des paramètres, permettent à la fonction de s’adapter et de produire un résultat différent selon ce qu’on lui donne.


    func direBonjour(prenom: String) {
        print("Bonjour, \(prenom) !")
    }

    direBonjour(prenom: "Mathieu") // Affiche : Bonjour, Mathieu !
  

Fonction avec valeur de retour

Une fonction avec valeur de retour est une fonction qui, après avoir exécuté son travail, renvoie un résultat que l’on peut utiliser ailleurs dans le programme. Ce résultat est appelé la valeur de retour.


    func additionner(a: Int, b: Int) -> Int {
        return a + b
    }

    let resultat = additionner(a: 5, b: 3)
    print(resultat) // Affiche : 8

Fonction avec paramètres variadiques

Une fonction avec paramètre variadique est une fonction qui peut accepter un nombre illimité de valeurs pour un paramètre donné. Cela signifie qu’on peut passer autant d’arguments qu’on le souhaite pour ce paramètre, sans devoir en fixer un nombre exact à l’avance.


    func moyenne(_ nombres: Double...) -> Double {
        var total = 0.0
        for nombre in nombres {
            total += nombre
        }
        return total / Double(nombres.count)
    }

    let moy = moyenne(1, 2, 3, 4, 5)
    print(moy) // Affiche : 3.0
  

Fonction avec paramètres par défaut

Une fonction avec un paramètre par défaut est une fonction où certains paramètres ont une valeur déjà définie si l’utilisateur n’en fournit pas lorsqu’il appelle la fonction. Ces valeurs par défaut sont utilisées automatiquement.


    func direBonjour(prenom: String = "Inconnu") {
        print("Bonjour, \(prenom) !")
    }

    direBonjour() // Affiche : Bonjour, Inconnu !
    direBonjour(prenom: "Mathieu") // Affiche : Bonjour, Mathieu !

Fonction avec paramètres inout

Une fonction avec paramètres inout est une fonction qui peut modifier directement la valeur d’une variable passée en argument. Cela fonctionne en permettant à la fonction d’accéder à l’intérieur de la variable au lieu d’utiliser une copie de sa valeur.


    func doubler(_ nombre: inout Int) {
        nombre *= 2
    }

    var x = 10
    doubler(&x)
    print(x) // Affiche : 20

Fonction avec valeur de retour optionnelle

Une fonction avec valeur de retour optionnelle est une fonction qui peut soit :

  • Renvoyer une valeur (comme une fonction classique),
  • Soit renvoyer rien (ou plutôt nil, qui signifie “pas de valeur”).
  • Cela permet de gérer des cas où une fonction peut échouer ou ne pas avoir de réponse valable.

    
        func diviser(_ a: Double, par b: Double) -> Double? {
            if b == 0 {
                return nil
            }
            return a / b
        }
    
        if let resultat = diviser(10, par: 2) {
            print(resultat) // Affiche : 5.0
        } else {
            print("Division par zéro !")
        }
    

    Fonction avec étiquettes d'argument personnalisées

    Les étiquettes d'argument permettent de nommer les paramètres d'une fonction de manière plus explicite lors de son appel. Elles rendent le code plus lisible et plus compréhensible.

    
        func direBonjour(a prenom: String) {
            print("Bonjour, \(prenom) !")
        }
    
        direBonjour(a: "Mathieu") // Affiche : Bonjour, Mathieu !
    

    Fonction avec étiquettes d'argument omises

    Il est possible d'omettre les étiquettes d'argument pour rendre le code plus concis. Cela est utile lorsque le nom du paramètre est déjà explicite.

    
        func direBonjour(_ prenom: String) {
            print("Bonjour, \(prenom) !")
        }
    
        direBonjour("Mathieu") // Affiche : Bonjour, Mathieu !
    

    Fonction avec étiquettes d'argument pour les paramètres suivants

    Il est possible de définir des étiquettes d'argument pour les paramètres suivants, afin de rendre le code plus lisible et plus compréhensible.

    
        func additionner(_ a: Int, et b: Int) -> Int {
            return a + b
        }
    
        let somme = additionner(5, et: 3)
        print(somme) // Affiche : 8
    

    Fonction avec étiquettes d'argument pour les paramètres précédents

    Il est possible de définir des étiquettes d'argument pour les paramètres précédents, afin de rendre le code plus lisible et plus compréhensible.

    
        func additionner(a: Int, _ b: Int) -> Int {
            return a + b
        }
    
        let total = additionner(a: 5, 3)
        print(total) // Affiche : 8
    

    Les structures de données

    Les structures de données en Swift sont des outils essentiels pour organiser, stocker et manipuler des informations dans un programme. Swift propose plusieurs structures intégrées, puissantes et flexibles, qui couvrent la majorité des besoins de développement.

    Les tableaux

    Un tableau est une liste ordonnée d’éléments de même type. Il permet de stocker plusieurs valeurs dans une seule variable.

    
      var fruits: [String] = ["Pomme", "Banane", "Orange"]
      fruits.append("Mangue") // Ajouter un élément
      fruits.remove(at: 1) // Supprimer l'élément à l'index 1
      print(fruits) // Affiche : ["Pomme", "Orange", "Mangue"]
      print(fruits[1]) // Affiche : Orange
      

    Les dictionnaires

    Un dictionnaire est une collection non ordonnée de paires clé-valeur. Chaque clé est unique et associée à une valeur.

    
      var capitales: [String: String] = ["France": "Paris", "Italie": "Rome"]
      capitales["Espagne"] = "Madrid" // Ajouter une clé et une valeur
      capitales["Italie"] = "Venise" // Modifier une valeur
      capitales.removeValue(forKey: "France") // Supprimer une clé
      print(capitales) // Affiche : ["Italie": "Venise", "Espagne": "Madrid"]
      print(capitales["Espagne"]) // Affiche : Optional("Madrid")
      print(capitales["Espagne"]!) // Affiche : Madrid
          

    Les ensembles (set)

    Un ensemble est une collection non ordonnée d’éléments uniques.

    
      var couleurs: Set< String > = ["Rouge", "Vert", "Bleu"]
      couleurs.insert("Jaune") // Ajouter un élément
      couleurs.remove("Vert") // Supprimer un élément
      print(couleurs) // Affiche : ["Rouge", "Bleu", "Jaune"]
            
    Exemple d'opérations sur les ensembles
    
      let a: Set = [1, 2, 3]
      let b: Set = [3, 4, 5]
      print(a.union(b)) // [1, 2, 3, 4, 5] (union)
      print(a.intersection(b)) // [3] (intersection)
      print(a.subtracting(b)) // [1, 2] (soustraction)
              

    Les tuples

    Un tuple regroupe plusieurs valeurs dans une seule entité. Contrairement aux tableaux, les éléments d’un tuple peuvent être de types différents.

    
      let personne = ("Alice", 25, true)
      print(personne.0) // Affiche : Alice
      print(personne.1) // Affiche : 25
      print(personne.2) // Affiche : true
    
      // Modifier une valeur
      personne.1 = 40
      print(personne.1) // Affiche : 40
              
    Exemple de tuple nommé
    
      let personne = (nom: "Alice", age: 25, estEtudiante: true)
      print(personne.nom) // Affiche : Alice
                

    Structures personnalisées (struct)

    Les structures (struct) sont l’un des types fondamentaux en Swift. Elles permettent de définir des types personnalisés qui peuvent contenir des propriétés (variables et constantes) et des méthodes (fonctions). Les struct sont largement utilisées pour organiser des données et ajouter des fonctionnalités dans un programme Swift.

    Déclaration et Utilisation des struct

    
      struct Point {
        var x: Double
        var y: Double
        
        // Méthode pour déplacer le point
        mutating func move(dx: Double, dy: Double) {
            x += dx
            y += dy
        }
      }
      
      // Création d'une instance
      var point = Point(x: 3.0, y: 4.0)
      
      // Affichage des coordonnées
      print("Point initial : (\(point.x), \(point.y))")
      
      // Déplacement du point
      point.move(dx: 2.0, dy: -1.0)
      print("Point après déplacement : (\(point.x), \(point.y))")
          
    Exemple de struct avec initialisateur personnalisé
    
      struct Rectangle {
        var width: Double
        var height: Double
        
        // Initialiseur personnalisé
        init(width: Double, height: Double) {
            self.width = width
            self.height = height
        }
        
        // Calcul de l'aire (propriété calculée)
        var area: Double {
            return width * height
        }
      }
      
      let rectangle = Rectangle(width: 5.0, height: 10.0)
      print("Aire du rectangle : \(rectangle.area)")
          
    Immutabilité et propriétés mutables

    Une instance d’une structure définie comme let est immutable. Les propriétés ne peuvent pas être modifiées.

    
      let immutablePoint = Point(x: 1.0, y: 2.0)
      immutablePoint.x = 5.0 // Erreur : Impossible de modifier une propriété d'une instance constante
          
    Structures imbriquées

    Une structure peut contenir d’autres structures. Par exemple, un rectangle peut être défini à partir de deux points.

    
      struct Point {
        var x: Double
        var y: Double
      }
      
      struct Rectangle {
          var topLeft: Point
          var bottomRight: Point
          
          var area: Double {
              let width = bottomRight.x - topLeft.x
              let height = bottomRight.y - topLeft.y
              return width * height
          }
      }
          
      let rect = Rectangle(topLeft: Point(x: 0, y: 10), bottomRight: Point(x: 5, y: 0))
      print("Aire du rectangle : \(rect.area)")
        

    Les classes

    Les classes sont des types de données de base en Swift, qui permettent de créer des objets avec des propriétés et des méthodes. Les classes sont des types référence, ce qui signifie que lorsqu’on les assigne à une variable ou qu’on les passe à une fonction, elles sont partagées plutôt que copiées.

    
      class Personne {
        var prenom: String
        var age: Int
    
        init(prenom: String, age: Int) {
            self.prenom = prenom
            self.age = age
        }
    
        func direBonjour() {
            print("Bonjour, je m'appelle \(prenom) et j'ai \(age) ans.")
        }
      }
      
      let personne = Personne(prenom: "Mathieu", age: 20)
      personne.direBonjour() // Affiche : Bonjour, je m'appelle Mathieu et j'ai 20 ans.
    
      personne.age = 27
      personne.direBonjour() // Affiche : Bonjour, je m'appelle Mathieu et j'ai 27 ans.
        

    Héritage

    Une classe peut hériter d’une autre classe pour partager et étendre ses fonctionnalités.

    
      class Animal {
        var name: String
        
        init(name: String) {
            self.name = name
        }
        
        func makeSound() {
            print("\(name) fait un bruit.")
        }
      }
      
      // Classe dérivée
      class Dog: Animal {
        func bark() {
            print("\(name) aboie.")
        }
        
        // Redéfinition d'une méthode
        override func makeSound() {
            print("\(name) fait : Woof!")
        }
      }
        
      let dog = Dog(name: "Rex")
      dog.makeSound()
      dog.bark()
        

    Déinitialiseur

    Un déinitialiseur (deinit) est une méthode spéciale utilisée dans les classes pour exécuter du code juste avant que l’instance de la classe ne soit détruite. Cela permet de libérer des ressources, fermer des connexions ou effectuer un nettoyage nécessaire.

    Syntaxe d’un déinitialiseur
    
      deinit {
        // Code de nettoyage
      }
        
    Exemple : gestion de ressources
    
      class FileHandler {
        var fileName: String
        
        init(fileName: String) {
            self.fileName = fileName
            print("Fichier \(fileName) ouvert.")
        }
        
        deinit {
            print("Fichier \(fileName) fermé.")
        }
      }
      
      // Bloc pour limiter la portée
      do {
        let handler = FileHandler(fileName: "document.txt")
        // À la fin du bloc, handler est libéré et le déinitialiseur est appelé
      }
        

    Le déinitialiseur est utile pour gérer la mémoire et nettoyer les ressources associées à une instance lorsqu’elle est détruite. Swift s’en charge automatiquement via l’ARC, mais le déinitialiseur permet d’ajouter un traitement spécifique si nécessaire.

    Les optionnels

    Les optionnels sont une fonctionnalité clé de Swift qui permet de gérer les valeurs manquantes ou inexistantes. Ils sont utilisés pour indiquer qu’une variable ou une constante peut soit contenir une valeur, soit être nulle (nil). Cela offre une manière sûre et explicite de travailler avec des données qui peuvent être absentes, tout en évitant les erreurs courantes liées à des pointeurs nuls.

    Déclaration d’un optionnel

    Un optionnel est déclaré en ajoutant un point d’interrogation (?) après le type de la variable.

    
      var name: String?  // Peut contenir une valeur de type String ou être nil
      name = "Alice"     // Contient une valeur
      name = nil         // Maintenant, aucune valeur n'est présente
        

    Accès aux valeurs optionnelles

    Pour utiliser la valeur d’un optionnel, il faut d’abord la “déballer” (unwrap). Il existe plusieurs façons de le faire.

    Déballage forcé (!)

    Utilisé lorsque vous êtes certain que l’optionnel contient une valeur. Cependant, si l’optionnel est nil, cela provoque une erreur à l’exécution.

    
      var name: String? = "Alice"
      print(name!)  // Affiche "Alice"
          

    ⚠️ Attention : Évitez d’utiliser le déballage forcé à moins d’être sûr à 100% que l’optionnel n’est pas nil.

    Déballage conditionnel (if let)

    Permet de vérifier si une valeur est présente et de la déballer de manière sécurisée.

    
      var name: String? = "Alice"
      if let unwrappedName = name {
          print("Bonjour, \(unwrappedName)!")
      } else {
          print("Aucun nom fourni.")
      }
          
    Déballage avec guard let

    Utilisé pour forcer une valeur non-nil dans une fonction ou un bloc de code et quitter si la condition échoue.

    
      func greetUser(name: String?) {
        guard let unwrappedName = name else {
            print("Nom manquant.")
            return
        }
        print("Bonjour, \(unwrappedName)!")
      }    
          
    Opérateur de coalescence (??)

    Permet de fournir une valeur par défaut si l’optionnel est nil.

    
      let username: String? = nil
      let displayName = username ?? "Utilisateur inconnu"
      print(displayName)  // Affiche "Utilisateur inconnu"
          
    Optionnels implicites

    Si une valeur optionnelle est garantie d’avoir une valeur après son initialisation, on peut utiliser un optionnel implicite en ajoutant un point d’exclamation (!) après le type.

    
      var email: String! = "test@example.com"
      print(email)  // Pas besoin de déballage explicite
          

    ⚠️ Attention : Cependant, l’utilisation des optionnels implicites doit être limitée, car elle comporte les mêmes risques que le déballage forcé.

    Protocole

    En Swift, un protocole est un modèle (ou contrat) qui définit un ensemble de propriétés, méthodes, ou autres exigences qu’une classe, une structure, ou une énumération doit implémenter. Les protocoles permettent d’organiser le code, de garantir un comportement uniforme entre les types, et de faciliter l’utilisation du polymorphisme.

    Déclaration d’un protocole

    Un protocole est déclaré à l’aide du mot-clé protocol, suivi de son nom et de ses exigences.

    
      protocol Vehicule {
        var nombreDeRoues: Int { get }
        func demarrer()
      }
          

    Adoption d’un protocole

    Une classe, une structure ou une énumération peut adopter un protocole en implémentant ses exigences.

    
      struct Voiture: Vehicule {
        var nombreDeRoues: Int = 4
        
        func demarrer() {
            print("La voiture démarre !")
        }
      }
      
      let maVoiture = Voiture()
      maVoiture.demarrer() // Affiche : La voiture démarre !
          

    Protocoles avec propriétés mutables

    Si une propriété doit être modifiable par une classe ou une structure adoptant le protocole, ajoute { get set }.

    
      protocol Personne {
        var nom: String { get set }
        func sePresenter()
      }
      
      struct Etudiant: Personne {
        var nom: String
        
        func sePresenter() {
            print("Bonjour, je m'appelle \(nom) !")
        }
      }
    
      var etudiant = Etudiant(nom: "Alice")
      etudiant.sePresenter() // Affiche : Bonjour, je m'appelle Alice !
          

    Utiliser des protocoles comme types

    Un protocole peut être utilisé comme type pour une variable, une constante ou un paramètre.

    
      func afficherDetails(vehicule: Vehicule) {
        print("Nombre de roues : \(vehicule.nombreDeRoues)")
        vehicule.demarrer()
      }
      
      let voiture = Voiture()
      afficherDetails(vehicule: voiture)
      // Affiche :
      // Nombre de roues : 4
      // La voiture démarre !
          

    Héritage de protocoles

    Un protocole peut hériter d’un ou plusieurs autres protocoles

    
      protocol VehiculeMotorise: Vehicule {
        var typeDeCarburant: String { get }
      }
      
      struct Moto: VehiculeMotorise {
        var nombreDeRoues: Int = 2
        var typeDeCarburant: String = "Essence"
        
        func demarrer() {
            print("La moto démarre !")
        }
      }
      
      let moto = Moto()
      print(moto.typeDeCarburant) // Affiche : Essence