Swift est le langage de programmation d'Apple qui combine la puissance d'Objective-C avec la sécurité des langages modernes. Contrairement à de nombreux autres langages, Swift propose des constructions uniques : les types valeur avec copie automatique, les Actors avec isolation au niveau du compilateur, les Property Wrappers pour réutiliser la logique des propriétés et la Programmation Orientée Protocole comme alternative à l'héritage classique. Selon la Documentation Apple Swift, ces fonctionnalités rendent le code plus sûr et plus performant.
Points Clés
Dans Swift, struct est un type valeur, tandis que class est un type référence. Cette différence fondamentale détermine le comportement lors de l'affectation, du passage aux fonctions et de la gestion de la mémoire. Struct est copié entièrement à chaque affectation, class est passé par référence — seul le pointeur vers l'objet dans le tas est copié.
Struct implémente automatiquement la sémantique de valeur — chaque instance est indépendante. La mutation d'une propriété de struct n'est possible que via var, et les méthodes qui modifient les propriétés doivent être marquées mutating. Struct ne prend pas en charge l'héritage mais peut se conformer aux protocoles. La bibliothèque standard de Swift utilise struct pour String, Array, Dictionary, Int, Bool — tous les types fondamentaux sont des types valeur.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// Copie complète — instances indépendantes
Class est stocké dans le tas et prend en charge l'égalité par référence. Plusieurs variables peuvent référencer le même objet, les modifications via une référence sont visibles via toutes les autres. Class prend en charge l'héritage, permet la redéfinition de méthodes (override), a deinit pour le nettoyage des ressources. Dans Swift, class est moins utilisée que struct — Apple recommande de commencer par struct et de passer à class seulement lorsque l'héritage ou la sémantique de référence sont nécessaires.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// Même référence — modifications visibles partout
Le choix entre struct et class dépend de la sémantique des données. Pour les configurations, modèles de données et états, utilisez struct — la copie est plus sûre et prévisible. Pour les objets avec identité (session utilisateur, connexion base de données) et lorsque l'héritage est nécessaire, utilisez class.
Actor est un type référence qui protège son état mutable contre l'accès concurrent. Le compilateur Swift garantit que les variables d'actor ne sont accessibles que depuis le même contexte d'actor — tout accès externe provoque une erreur de compilation. Cela prévient les conditions de course au niveau du langage, sans verrous manuels.
Toutes les propriétés et méthodes d'actor sont isolées par défaut. L'appel d'une méthode d'actor depuis l'extérieur nécessite await — Swift bascule l'exécution vers l'actor et inversement. L'actor synchronise l'accès séquentiellement : les requêtes de différentes tâches sont mises en file d'attente. Cela garantit un état cohérent à l'intérieur de l'actor. Les méthodes non isolées (nonisolated) peuvent être appelées sans await mais ne peuvent pas accéder aux propriétés mutables.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "Compte Bancaire"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — atomique, sans condition de course
Actor est utile pour les gestionnaires d'état, caches et services réseau où plusieurs tâches lisent et écrivent des données concurrentiellement. Contrairement à la synchronisation manuelle via NSLock ou DispatchQueue, l'actor est vérifié à la compilation — il est impossible de faire une erreur. Les erreurs d'isolation apparaissent à la compilation, pas à l'exécution.
Property Wrapper est une annotation qui ajoute du comportement à une propriété sans modifier sa déclaration. SwiftUI et Combine fournissent des wrappers intégrés pour gérer l'état des vues. Le compilateur génère du code qui encapsule l'accès à la propriété, ajoutant une logique d'observation, de synchronisation ou de transformation.
@Published est utilisé dans les classes conformes à ObservableObject pour publier automatiquement les changements. Les abonnés Combine reçoivent des notifications à chaque mise à jour. @State est l'état local d'une vue SwiftUI. SwiftUI redessine automatiquement la vue lorsque @State change. @Binding crée une connexion bidirectionnelle entre une vue parent et enfant sans posséder les données.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "Bonjour"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Incrémenter", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Compteur : \(count)")
}
}
Les Property Wrappers personnalisés sont créés avec @propertyWrapper et une structure avec un champ wrappedValue. Cela permet de réutiliser la logique : validation, normalisation, stockage UserDefaults. Les Property Wrappers réduisent considérablement le code boilerplate et rendent l'intention du développeur explicite via l'annotation.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — valeur automatiquement limitée
Generics est un mécanisme puissant de Swift pour écrire du code flexible sans perdre la sécurité de typage. Les fonctions et types génériques fonctionnent avec tout type satisfaisant les contraintes. La Programmation Orientée Protocole (POP) est un paradigme Swift où protocol + extension remplacent l'héritage classique des classes, offrant une composition de comportement.
Swift permet de contraindre les paramètres génériques avec des protocoles (clauses where). Protocole avec type associé est l'équivalent générique pour les protocoles : le type concret est déterminé au moment de la conformité. C'est la base pour les collections et algorithmes type-safe. La bibliothèque standard de Swift utilise activement les generics : Array, Dictionary, Optional — tous sont des types génériques.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — une méthode générique pour tous les types Equatable
L'extension de protocole est un mécanisme clé de POP. Dans une extension, on peut fournir une implémentation par défaut des méthodes du protocole. Les types conformes au protocole obtiennent cette implémentation gratuitement et peuvent la redéfinir si nécessaire. Cela permet de construire des hiérarchies de comportement sans héritage de classes — une alternative à l'héritage multiple, qui n'existe pas en Swift.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Conduite à \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Vol à \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Conduite à 15.0 km/h" — par défaut
let plane = Plane()
plane.drive() // "Vol à 900.0 km/h" — redéfini
POP avec extension permet la composition via de multiples protocoles — un type peut se conformer à plusieurs protocoles, obtenant leurs implémentations. C'est plus flexible que l'héritage unique de classes où toute la hiérarchie est fixe. La bibliothèque standard de Swift est construite sur POP : CustomStringConvertible, Equatable, Hashable, Codable — tous sont des protocoles avec extensions.
Enum dans Swift est une construction nettement plus puissante que dans d'autres langages. Chaque cas peut avoir des valeurs associées — un ensemble arbitraire de données de n'importe quel type. Combiné avec extension et pattern matching, enum devient la base pour modéliser les états, les résultats d'opérations et les machines à états finis.
Les valeurs associées permettent à chaque cas de stocker des données uniques. Par exemple, le cas loading ne contient pas de données, success contient le résultat, failure contient une erreur. Le pattern matching via switch extrait les valeurs associées et traite chaque cas. Le compilateur vérifie l'exhaustivité (exhaustive switch) — cela élimine les états oubliés.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("Chargement...")
case .success(let data):
print("Reçu : \(data)")
case .failure(let error):
print("Erreur : \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("Vidéo \(url) durée \(duration)s")
}
Swift permet d'ajouter des méthodes, des propriétés calculées et la conformité aux protocoles via extension pour enum. Cela ne nécessite pas de modifier la déclaration originale — le code reste propre et modulaire. Les méthodes dans l'extension peuvent travailler avec des valeurs associées, calculer des données dérivées et implémenter la logique métier.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "Image \(w)x\(h)"
case .video(_, let d):
return "Vidéo \(d)s"
case .audio(_, let b):
return "Audio \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "Vidéo 120.0s"
Enum avec valeurs associées + extension est le remplacement des classes sealed de Kotlin ou des unions discriminées d'autres langages. Swift utilise cette combinaison pour Result (un type standard de la bibliothèque standard), Optional (Optional est un enum avec .none et .some) et la gestion d'état dans SwiftUI. Le pattern matching garantit que tous les états sont traités — les erreurs d'exécution sont impossibles.
Questions Fréquentes
Struct — type valeur, copié lors de l'affectation, ne prend pas en charge l'héritage. Class — type référence, passé par référence, prend en charge l'héritage, deinit et la vérification d'identité (===). Apple recommande de commencer par struct et de passer à class uniquement lorsque la sémantique de référence est nécessaire.
Actor — type référence thread-safe. Le compilateur interdit l'accès concurrent à l'état mutable depuis différents contextes. Utilisez Actor pour les gestionnaires d'état, caches et services accessibles par plusieurs tâches. L'appel de méthodes d'actor nécessite await.
Principaux : @State — état local de la vue ; @Binding — connexion aux données sans possession ; @Published — publication des changements dans ObservableObject ; @StateObject — possession d'ObservableObject ; @EnvironmentObject — dépendance de l'environnement. Les wrappers personnalisés sont créés via @propertyWrapper.
POP — un paradigme où protocol + extension définissent le comportement au lieu de l'héritage de classe. Un type se conforme à plusieurs protocoles, obtenant leurs implémentations gratuitement. C'est la composition plutôt que l'héritage — plus flexible et plus sûr. La bibliothèque standard de Swift (Equatable, Codable) est construite sur POP.
Enum dans Swift prend en charge les valeurs associées — chaque cas peut stocker des données arbitraires de différents types. Combiné avec le pattern matching (switch), cela fournit un modèle puissant pour les états, les erreurs et le Domain-Driven Design. Optional et Result sont des enums standard de Swift avec valeurs associées.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.