En Swift, le contrôle d’accès est un mécanisme essentiel qui détermine la visibilité et l’accessibilité des différentes parties de votre code, telles que les classes, les structures, les fonctions et les propriétés. Il vous permet de restreindre l’accès à certaines parties de votre code afin de protéger son intégrité et d’encapsuler les détails d’implémentation.
public class PublicClass {
open func openMethod() {
// Peut être surchargée en dehors du module
}
public func publicMethod() {
// Peut être appelée en dehors du module, mais ne peut être surchargée qu'à l'intérieur
}
internal func internalMethod() {
// Accessible uniquement au sein du module
}
fileprivate func fileprivateMethod() {
// Accessible uniquement dans ce fichier source
}
private func privateMethod() {
// Accessible uniquement dans cette classe
}
}
En comprenant et en appliquant correctement ces niveaux d’accès, vous pouvez créer des applications iOS plus robustes et mieux structurées, tout en protégeant les parties sensibles de votre code.
En Swift, les propriétés peuvent utiliser des accesseurs (get) et des mutateurs (set) pour contrôler l’accès en lecture ou en écriture.
class Person {
private var _age: Int = 0
var age: Int {
get { return _age }
set { _age = newValue }
}
}
let person = Person()
person.age = 25
print(person.age) // Affiche : 25
En Swift, les méthodes de type sont des fonctions qui appartiennent directement à une classe ou une structure, et non à une instance spécifique. Pour les définir, on utilise les mots-clés static ou class.
Ici, additionner est une méthode de type définie avec static. Elle peut être appelée directement sur le type Math sans créer d’instance.
struct Math {
static func additionner(a: Int, b: Int) -> Int {
return a + b
}
}
let resultat = Math.additionner(a: 5, b: 3)
print(resultat) // Affiche : 8
Dans cet exemple, la méthode de type description est définie avec class, permettant aux sous-classes de la surcharger. Ainsi, Chien fournit sa propre implémentation de description.
class Animal {
class func description() -> String {
return "Ceci est un animal."
}
}
class Chien: Animal {
override class func description() -> String {
return "Ceci est un chien."
}
}
print(Animal.description()) // Affiche : Ceci est un animal.
print(Chien.description()) // Affiche : Ceci est un chien.
En Swift, une propriété paresseuse (lazy property) est une propriété dont la valeur n’est calculée et initialisée qu’au moment où elle est utilisée pour la première fois. Cela permet d’optimiser les performances en retardant des calculs coûteux ou en différant l’initialisation d’objets jusqu’à ce qu’ils soient réellement nécessaires.
Pour déclarer une propriété paresseuse, on utilise le mot-clé lazy avant la déclaration de la propriété. Les propriétés paresseuses doivent toujours être des variables (var), car leur valeur peut ne pas être initialisée au moment de la création de l’instance.
class DataLoader {
lazy var data: [String] = loadData()
func loadData() -> [String] {
// Simuler un chargement de données
print("Chargement des données...")
return ["Donnée1", "Donnée2", "Donnée3"]
}
}
let loader = DataLoader()
// À ce stade, la propriété 'data' n'est pas encore initialisée
print(loader.data) // Déclenche l'appel à 'loadData' et initialise 'data'
En Swift, le mot-clé override est utilisé pour indiquer qu’une méthode, une propriété ou un sous-script d’une classe dérivée redéfinit l’implémentation héritée de sa classe parente. Cela permet à une sous-classe de fournir une version spécifique d’une fonctionnalité définie dans sa superclasse.
L’utilisation du mot-clé override garantit que vous redéfinissez intentionnellement une méthode ou une propriété existante dans la superclasse. Swift vérifie alors que la méthode ou la propriété que vous tentez de redéfinir existe bien dans la superclasse, évitant ainsi les erreurs potentielles dues à des fautes de frappe ou à des incohérences.
class Animal {
func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Bark")
}
}
let dog = Dog()
dog.makeSound() // Affiche : Bark
Il est possible, dans une méthode redéfinie, d’appeler l’implémentation de la superclasse en utilisant le mot-clé super. Cela peut être utile si vous souhaitez étendre le comportement hérité plutôt que de le remplacer complètement.
class Animal {
func makeSound() {
print("Son d'animal")
}
}
class Chat: Animal {
override func makeSound() {
super.makeSound()
print("Miauler")
}
}
let monChat = Chat()
monChat.makeSound()
// Affiche :
// Son d'animal
// Miauler
class Animal {
final func makeSound() {
print("Son d'animal")
}
}
Toute tentative de redéfinir makeSound() dans une sous-classe entraînera une erreur de compilation.
En Swift, les wrappers de propriété (property wrappers) sont une fonctionnalité qui permet d’ajouter une couche de logique autour de la gestion des propriétés. Ils facilitent la réutilisation de comportements courants, comme la validation des données ou la gestion du stockage, sans répéter le même code pour chaque propriété.
Un wrapper de propriété est une structure ou une classe qui encapsule une propriété, en ajoutant une logique personnalisée lors de l’accès en lecture ou en écriture. Cela permet de séparer la logique de gestion des données de la définition de la propriété elle-même.
Pour créer un wrapper de propriété, on utilise l’attribut @propertyWrapper et on définit une propriété nommée wrappedValue qui représente la valeur encapsulée. Par exemple, voici un wrapper qui garantit que toutes les chaînes de caractères assignées sont automatiquement mises en majuscules :
@propertyWrapper
struct Majuscule {
private var valeur: String = ""
var wrappedValue: String {
get { valeur }
set { valeur = newValue.uppercased() }
}
}
Dans cet exemple, chaque fois qu’une nouvelle valeur est assignée à wrappedValue, elle est convertie en majuscules avant d’être stockée.
Une fois le wrapper défini, on peut l’appliquer à une propriété en utilisant l’annotation correspondante. Par exemple, si nous avons une structure Utilisateur et que nous voulons que le nom soit toujours en majuscules :
struct Utilisateur {
@Majuscule var nom: String
}
var utilisateur = Utilisateur()
utilisateur.nom = "Jean Dupont"
print(utilisateur.nom) // Affiche : JEAN DUPONT
Ici, la propriété nom utilise le wrapper Majuscule, garantissant que toute valeur assignée est automatiquement convertie en majuscules.
Il est important de noter que les wrappers de propriété ne peuvent pas participer à la gestion des erreurs et qu’il n’est pas possible d’appliquer plusieurs wrappers à une même propriété.
En Swift, la gestion des erreurs permet de répondre aux situations inattendues qui peuvent survenir lors de l’exécution d’un programme, comme l’échec de la lecture d’un fichier ou une connexion réseau interrompue. Swift offre des mécanismes robustes pour détecter, propager et gérer ces erreurs de manière contrôlée.
Les erreurs en Swift sont représentées par des types qui adoptent le protocole Error. Généralement, on utilise des énumérations pour définir les différentes erreurs possibles :
enum ErreurFichier: Error {
case fichierIntrouvable
case lectureImpossible
}
Une fonction susceptible de générer une erreur doit être marquée avec le mot-clé throws. Pour signaler une erreur, on utilise l’instruction throw :
func lireFichier(chemin: String) throws -> String {
// Vérifier si le fichier existe
guard fichierExiste(chemin) else {
throw ErreurFichier.fichierIntrouvable
}
// Tentative de lecture du fichier
// ...
}
Lorsqu’on appelle une fonction qui peut générer une erreur, on utilise le mot-clé try. Cet appel doit être effectué dans un bloc do-catch pour gérer les éventuelles erreurs:
do {
let contenu = try lireFichier(chemin: "chemin/vers/fichier.txt")
print(contenu)
} catch ErreurFichier.fichierIntrouvable {
print("Erreur : Fichier introuvable.")
} catch ErreurFichier.lectureImpossible {
print("Erreur : Lecture du fichier impossible.")
} catch {
print("Erreur inattendue : \(error).")
}
Quand une fonction peut provoquer une erreur (comme lire un fichier), on peut utiliser try de plusieurs manières :
if let contenu = try? lireFichier(chemin: "chemin/vers/fichier.txt") {
print(contenu)
} else {
print("Erreur lors de la lecture du fichier.")
}
Ici, si le fichier est trouvé, on affiche son contenu. Sinon, on affiche un message d’erreur. C’est simple et sécurisé, parfait quand tu n’as pas besoin de savoir quelle est l’erreur exactement.
let contenu = try! lireFichier(chemin: "chemin/vers/fichier.txt")
print(contenu)
Ici, si le fichier n’existe pas ou ne peut pas être lu, l’app crashe immédiatement. Ce n’est pas recommandé, sauf dans des cas très précis (comme pour des fichiers intégrés dans l’app et sûrs à 100 %).
La gestion des erreurs en Swift est essentielle pour créer des applications robustes et fiables. En définissant clairement les erreurs possibles, en les propageant de manière appropriée et en les gérant avec des blocs do-catch, vous pouvez anticiper et réagir efficacement aux situations inattendues qui pourraient survenir lors de l’exécution de votre programme.
En Swift, les types spéciaux Any et AnyObject offrent une flexibilité accrue lors de la manipulation de données de types variés.
let collectionMixte: [Any] = [42, "Bonjour", 3.14, true]
class MaClasse {}
let tableauObjets: [AnyObject] = [MaClasse(), NSString(string: "Bonjour")]
Lorsque vous récupérez des valeurs à partir d’une collection de Any ou AnyObject, il est souvent nécessaire de les convertir vers leur type d’origine
let collectionMixte: [Any] = [42, "Bonjour", 3.14]
if let premierÉlément = collectionMixte[0] as? Int {
print("Le premier élément est un entier : \(premierÉlément)")
}
En Swift, les extensions permettent d’ajouter de nouvelles fonctionnalités à des classes, structures, énumérations ou protocoles existants, même si vous n’avez pas accès à leur code source original. Cette capacité est particulièrement utile pour enrichir des types prédéfinis ou provenant de bibliothèques tierces, sans modifier leur implémentation initiale.
Ajouter des méthodes d’instance et de type : Vous pouvez introduire de nouvelles méthodes qui étendent les capacités d’un type existant.
extension Int {
func estPair() -> Bool {
return self % 2 == 0
}
}
let nombre = 4
print(nombre.estPair()) // Affiche : true
Ajouter des propriétés calculées : Vous pouvez définir des propriétés qui calculent leur valeur à partir d’autres propriétés existantes.
extension Double {
var enKilomètres: Double {
return self * 1_000.0
}
}
let distance = 5.0
print("\(distance.enKilomètres) mètres") // Affiche : 5000.0 mètres
Fournir des implémentations par défaut pour des protocoles : En utilisant des extensions, vous pouvez offrir des implémentations par défaut pour les méthodes d’un protocole, réduisant ainsi le besoin d’implémentations répétitives.
protocol Identifiable {
var id: String { get }
func afficherID()
}
extension Identifiable {
func afficherID() {
print("Mon identifiant est \(id).")
}
}
struct Utilisateur: Identifiable {
var id: String
}
let utilisateur = Utilisateur(id: "12345")
utilisateur.afficherID() // Affiche : Mon identifiant est 12345.
Conformer un type existant à un protocole : Les extensions permettent de déclarer qu’un type existant adopte un protocole, et de fournir les implémentations nécessaires.
struct Point {
var x: Double
var y: Double
}
extension Point: CustomStringConvertible {
var description: String {
return "Point(x: \(x), y: \(y))"
}
}
let point = Point(x: 3.0, y: 4.0)
print(point) // Affiche : Point(x: 3.0, y: 4.0)
Les extensions sont un outil puissant en Swift, offrant une flexibilité accrue pour adapter et enrichir les types existants selon les besoins spécifiques de votre application.