# Top 25 questions d'entretien Swift pour développeurs iOS > Préparez vos entretiens iOS avec les 25 questions Swift les plus posées : optionals, closures, ARC, protocols, async/await et patterns avancés. - Published: 2026-01-29 - Updated: 2026-03-28 - Author: SharpSkill - Tags: swift, ios, entretien, interview, apple - Reading time: 15 min --- Les entretiens techniques iOS mettent à l'épreuve la maîtrise de Swift et de ses concepts fondamentaux. Ces 25 questions couvrent les sujets les plus fréquemment abordés par les recruteurs, des bases du langage aux patterns avancés de concurrence. > **Comment utiliser ce guide** > > Chaque question est accompagnée d'une réponse détaillée et d'exemples de code. Les questions sont organisées par niveau de difficulté croissant, des fondamentaux aux concepts avancés. ## Les fondamentaux Swift ### 1. Quelle est la différence entre `let` et `var` ? `let` déclare une constante dont la valeur ne peut pas être modifiée après l'initialisation, tandis que `var` déclare une variable mutable. Pour les types référence (classes), `let` empêche la réassignation de la référence mais pas la modification de l'objet. ```swift // Constantes.swift // let = constante, valeur immuable let maximumAttempts = 3 // maximumAttempts = 5 // ❌ Erreur de compilation // var = variable, valeur modifiable var currentAttempt = 0 currentAttempt += 1 // ✅ OK // Attention aux types référence class User { var name: String init(name: String) { self.name = name } } let user = User(name: "Alice") user.name = "Bob" // ✅ OK - on modifie l'objet, pas la référence // user = User(name: "Charlie") // ❌ Erreur - réassignation interdite ``` **Bonne pratique** : utiliser `let` par défaut et ne passer à `var` que si la mutation est nécessaire. Cela rend le code plus prévisible et plus facile à raisonner. ### 2. Expliquez les optionals en Swift Les optionals représentent l'absence possible de valeur. Un optional peut contenir soit une valeur du type spécifié, soit `nil`. Swift utilise les optionals pour garantir la sécurité à la compilation en forçant la gestion explicite des cas où une valeur pourrait être absente. ```swift // Optionals.swift // Déclaration d'un optional avec ? var username: String? = nil // Peut contenir un String ou nil // Optional binding avec if let (unwrapping sécurisé) if let name = username { print("Bonjour, \(name)") // Exécuté seulement si username != nil } else { print("Utilisateur anonyme") } // Guard let pour un early return func greet(user: String?) { guard let name = user else { print("Pas d'utilisateur") return // Sortie anticipée si nil } print("Bonjour, \(name)") // name est garanti non-nil ici } // Nil-coalescing operator (??) pour valeur par défaut let displayName = username ?? "Anonyme" // Force unwrapping (!) - DANGEREUX, à éviter // let forced = username! // Crash si nil ``` ### 3. Quelle différence entre `struct` et `class` ? Les `struct` sont des types valeur (copiés lors de l'assignation) tandis que les `class` sont des types référence (partagent la même instance). Cette distinction fondamentale impacte la performance, la gestion mémoire et le comportement du code. ```swift // ValueVsReference.swift // Struct = type valeur (copie) struct Point { var x: Int var y: Int } var p1 = Point(x: 10, y: 20) var p2 = p1 // Copie indépendante p2.x = 100 // Ne modifie que p2 print(p1.x) // 10 - p1 inchangé // Class = type référence (partage) class Rectangle { var width: Int var height: Int init(width: Int, height: Int) { self.width = width self.height = height } } let r1 = Rectangle(width: 10, height: 20) let r2 = r1 // Même instance partagée r2.width = 100 // Modifie l'instance commune print(r1.width) // 100 - r1 aussi modifié ! ``` **Quand utiliser quoi** : - **Struct** : données simples, valeurs immuables, pas besoin d'héritage - **Class** : identité importante, héritage requis, comportement partagé ### 4. Comment fonctionne le pattern matching avec `switch` ? Le `switch` de Swift est puissant et exhaustif : il doit couvrir tous les cas possibles. Il supporte le pattern matching sur les types, les ranges, les tuples et les conditions additionnelles avec `where`. ```swift // PatternMatching.swift enum NetworkError: Error { case timeout case serverError(code: Int) case noConnection } func handleError(_ error: NetworkError) { switch error { case .timeout: print("Requête expirée") case .serverError(let code) where code >= 500: print("Erreur serveur critique: \(code)") case .serverError(let code): print("Erreur serveur: \(code)") case .noConnection: print("Pas de connexion") } // Pas besoin de default : tous les cas sont couverts } // Pattern matching sur ranges et tuples let point = (x: 5, y: 10) switch point { case (0, 0): print("Origine") case (let x, 0): print("Sur l'axe X à \(x)") case (0...10, 0...10): print("Dans le carré 10x10") default: print("Ailleurs") } ``` ### 5. Expliquez les closures et leur syntaxe Les closures sont des blocs de code autonomes qui capturent et stockent des références aux variables de leur contexte. Elles sont l'équivalent des lambdas ou fonctions anonymes dans d'autres langages. ```swift // Closures.swift // Syntaxe complète let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in return a + b } // Syntaxe raccourcie (type inféré, return implicite) let multiply: (Int, Int) -> Int = { $0 * $1 } // Trailing closure syntax let numbers = [3, 1, 4, 1, 5] let sorted = numbers.sorted { $0 > $1 } // [5, 4, 3, 1, 1] // Closure qui capture une variable func makeCounter() -> () -> Int { var count = 0 // Variable capturée return { count += 1 // La closure "ferme" sur count return count } } let counter = makeCounter() print(counter()) // 1 print(counter()) // 2 - count est mémorisé entre les appels ``` > **Capture semantics** > > Les closures capturent les variables par référence par défaut. Pour capturer par valeur, utilisez une capture list : `{ [count] in ... }`. ## Gestion mémoire et ARC ### 6. Comment fonctionne ARC (Automatic Reference Counting) ? ARC gère automatiquement la mémoire en comptant les références fortes vers chaque instance de classe. Quand le compteur tombe à zéro, l'instance est désallouée. Contrairement au garbage collection, ARC est déterministe et prévisible. ```swift // ARC.swift class Person { let name: String init(name: String) { self.name = name print("\(name) est initialisé") } deinit { print("\(name) est désalloué") } } // Démonstration du cycle de vie var person1: Person? = Person(name: "Alice") // refCount = 1 var person2 = person1 // refCount = 2 person1 = nil // refCount = 1 (pas désalloué) person2 = nil // refCount = 0 → deinit appelé // Output: "Alice est désalloué" ``` ### 7. Qu'est-ce qu'un retain cycle et comment l'éviter ? Un retain cycle (cycle de rétention) se produit quand deux objets se référencent mutuellement avec des références fortes, empêchant leur désallocation. Les mots-clés `weak` et `unowned` brisent ces cycles. ```swift // RetainCycle.swift class Department { let name: String var manager: Employee? // Référence forte init(name: String) { self.name = name } deinit { print("Department \(name) désalloué") } } class Employee { let name: String // weak évite le retain cycle - peut devenir nil weak var department: Department? init(name: String) { self.name = name } deinit { print("Employee \(name) désalloué") } } // Sans weak : retain cycle → fuite mémoire // Avec weak : désallocation correcte var dept: Department? = Department(name: "Engineering") var emp: Employee? = Employee(name: "Bob") dept?.manager = emp emp?.department = dept dept = nil // ✅ Désalloué grâce à weak emp = nil // ✅ Désalloué ``` ### 8. Quelle différence entre `weak` et `unowned` ? Les deux brisent les retain cycles, mais avec des garanties différentes. `weak` est optionnel et devient `nil` si l'objet référencé est désalloué. `unowned` suppose que l'objet existe toujours et crashe s'il est accédé après désallocation. ```swift // WeakVsUnowned.swift class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } } class CreditCard { let number: String // unowned car une carte existe toujours avec son client unowned let customer: Customer init(number: String, customer: Customer) { self.number = number self.customer = customer } } // La carte ne peut pas exister sans client let customer = Customer(name: "Alice") customer.card = CreditCard(number: "1234", customer: customer) // Si customer est désalloué, accéder à card.customer crasherait ``` **Règle** : utiliser `weak` par défaut. N'utiliser `unowned` que si la durée de vie de l'objet référencé est garantie supérieure ou égale. ## Protocols et génériques ### 9. Expliquez les protocols en Swift Les protocols définissent un contrat (propriétés et méthodes requises) que les types conformes doivent implémenter. Ils permettent le polymorphisme et sont la base de la programmation orientée protocole (POP) en Swift. ```swift // Protocols.swift // Définition d'un protocol protocol Drawable { var color: String { get set } // Propriété requise (lecture/écriture) func draw() // Méthode requise } // Extension de protocol avec implémentation par défaut extension Drawable { func draw() { print("Dessin par défaut en \(color)") } } // Conformité au protocol struct Circle: Drawable { var color: String var radius: Double // draw() hérite de l'implémentation par défaut } struct Square: Drawable { var color: String var side: Double // Override de l'implémentation par défaut func draw() { print("Carré \(color) de côté \(side)") } } // Utilisation polymorphique let shapes: [Drawable] = [Circle(color: "rouge", radius: 5), Square(color: "bleu", side: 10)] shapes.forEach { $0.draw() } ``` ### 10. Qu'est-ce qu'un associated type ? Les associated types permettent aux protocols de définir des types génériques qui seront spécifiés par les types conformes. C'est le mécanisme qui rend les protocols comme `Collection` si flexibles. ```swift // AssociatedTypes.swift // Protocol avec type associé protocol Container { associatedtype Item // Type défini par le conformant var items: [Item] { get set } mutating func add(_ item: Item) func count() -> Int } // Implémentation avec Item = String struct StringBox: Container { typealias Item = String // Optionnel, Swift peut inférer var items: [String] = [] mutating func add(_ item: String) { items.append(item) } func count() -> Int { items.count } } // Implémentation avec Item = Int struct IntStack: Container { var items: [Int] = [] mutating func add(_ item: Int) { items.append(item) } func count() -> Int { items.count } } ``` ### 11. Comment fonctionnent les génériques ? Les génériques permettent d'écrire du code flexible et réutilisable qui fonctionne avec n'importe quel type. Ils évitent la duplication de code tout en préservant la sécurité des types à la compilation. ```swift // Generics.swift // Fonction générique func swap(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp } // Contrainte de type avec where func findIndex(of item: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == item { return index } // Equatable requis pour == } return nil } // Struct générique struct Queue { private var elements: [Element] = [] mutating func enqueue(_ element: Element) { elements.append(element) } mutating func dequeue() -> Element? { guard !elements.isEmpty else { return nil } return elements.removeFirst() } } var intQueue = Queue() intQueue.enqueue(1) intQueue.enqueue(2) print(intQueue.dequeue()) // Optional(1) ``` ### 12. Expliquez le protocol `Codable` `Codable` (alias de `Encodable & Decodable`) permet la sérialisation automatique des types Swift vers et depuis des formats comme JSON. Le compilateur génère l'implémentation si toutes les propriétés sont elles-mêmes Codable. ```swift // Codable.swift struct User: Codable { let id: Int let name: String let email: String let createdAt: Date // CodingKeys pour mapper les noms JSON différents enum CodingKeys: String, CodingKey { case id case name case email case createdAt = "created_at" // snake_case → camelCase } } // Décodage JSON let json = """ { "id": 1, "name": "Alice", "email": "alice@example.com", "created_at": "2026-01-15T10:30:00Z" } """.data(using: .utf8)! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 do { let user = try decoder.decode(User.self, from: json) print(user.name) // "Alice" } catch { print("Erreur de décodage: \(error)") } // Encodage vers JSON let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let data = try encoder.encode(user) ``` ## Concurrence et async/await ### 13. Comment fonctionne async/await en Swift ? `async/await` simplifie le code asynchrone en permettant d'écrire des opérations non-bloquantes de manière séquentielle. Une fonction `async` peut être suspendue sans bloquer le thread, permettant à d'autres tâches de s'exécuter. ```swift // AsyncAwait.swift // Fonction asynchrone func fetchUser(id: Int) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! // await suspend l'exécution jusqu'à la réponse let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw NetworkError.invalidResponse } return try JSONDecoder().decode(User.self, from: data) } // Appel depuis un contexte async func loadUserProfile() async { do { let user = try await fetchUser(id: 42) print("Utilisateur: \(user.name)") } catch { print("Erreur: \(error)") } } // Appel depuis un contexte synchrone avec Task func buttonTapped() { Task { await loadUserProfile() } } ``` ### 14. Qu'est-ce qu'un Actor ? Les actors sont des types référence qui protègent leur état interne contre les accès concurrents. Ils garantissent qu'une seule tâche à la fois peut accéder à leurs propriétés mutables, éliminant les data races. ```swift // Actors.swift // Actor protège son état automatiquement actor BankAccount { private var balance: Double = 0 func deposit(_ amount: Double) { balance += amount // Accès thread-safe automatique } func withdraw(_ amount: Double) -> Bool { guard balance >= amount else { return false } balance -= amount return true } func getBalance() -> Double { return balance } } // Utilisation - await requis pour accéder à l'actor let account = BankAccount() Task { await account.deposit(100) let success = await account.withdraw(30) let balance = await account.getBalance() print("Solde: \(balance)") // 70 } ``` ### 15. Expliquez `Task` et `TaskGroup` `Task` crée une unité de travail asynchrone. `TaskGroup` permet d'exécuter plusieurs tâches en parallèle et de collecter leurs résultats. ```swift // TaskGroup.swift // Task simple let task = Task { return await fetchUser(id: 1) } let user = try await task.value // TaskGroup pour paralléliser func fetchMultipleUsers(ids: [Int]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in // Lance toutes les requêtes en parallèle for id in ids { group.addTask { try await fetchUser(id: id) } } // Collecte les résultats au fur et à mesure var users: [User] = [] for try await user in group { users.append(user) } return users } } // Les 3 requêtes s'exécutent en parallèle let users = try await fetchMultipleUsers(ids: [1, 2, 3]) ``` ### 16. Comment fonctionne `@MainActor` ? `@MainActor` garantit qu'un code s'exécute sur le thread principal. C'est essentiel pour les mises à jour UI qui doivent toujours s'exécuter sur le main thread. ```swift // MainActor.swift // Classe UI annotée @MainActor @MainActor class UserViewModel: ObservableObject { @Published var user: User? @Published var isLoading = false @Published var error: String? func loadUser() async { isLoading = true // ✅ Sur main thread automatiquement do { // Opération réseau sur un thread background user = try await fetchUser(id: 42) } catch { self.error = error.localizedDescription } isLoading = false // ✅ Retour sur main thread automatique } } // Ou pour une fonction spécifique func updateUI() async { await MainActor.run { // Ce bloc s'exécute sur le main thread label.text = "Mise à jour" } } ``` ## Patterns et architecture ### 17. Expliquez le pattern Delegate Le pattern Delegate permet à un objet de déléguer certaines responsabilités à un autre objet. C'est omniprésent dans UIKit (UITableViewDelegate, UITextFieldDelegate, etc.). ```swift // DelegatePattern.swift // 1. Définir le protocol du delegate protocol DownloadManagerDelegate: AnyObject { func downloadDidStart() func downloadDidProgress(_ progress: Double) func downloadDidComplete(data: Data) func downloadDidFail(error: Error) } // 2. Classe qui utilise le delegate class DownloadManager { // weak pour éviter les retain cycles weak var delegate: DownloadManagerDelegate? func startDownload(url: URL) { delegate?.downloadDidStart() // Simulation de téléchargement Task { for progress in stride(from: 0.0, to: 1.0, by: 0.1) { try await Task.sleep(nanoseconds: 100_000_000) delegate?.downloadDidProgress(progress) } delegate?.downloadDidComplete(data: Data()) } } } // 3. Classe qui implémente le delegate class ViewController: UIViewController, DownloadManagerDelegate { let manager = DownloadManager() override func viewDidLoad() { super.viewDidLoad() manager.delegate = self // Se déclare comme delegate } func downloadDidStart() { print("Démarré") } func downloadDidProgress(_ progress: Double) { print("\(progress * 100)%") } func downloadDidComplete(data: Data) { print("Terminé") } func downloadDidFail(error: Error) { print("Erreur: \(error)") } } ``` ### 18. Qu'est-ce que le pattern MVVM ? MVVM (Model-View-ViewModel) sépare la logique de présentation de la vue. Le ViewModel expose des données observables que la View affiche, sans connaître les détails de la View. ```swift // MVVM.swift // Model struct Article: Identifiable { let id: UUID let title: String let content: String let publishedAt: Date } // ViewModel @MainActor class ArticleListViewModel: ObservableObject { @Published private(set) var articles: [Article] = [] @Published private(set) var isLoading = false @Published var errorMessage: String? private let repository: ArticleRepository init(repository: ArticleRepository = .shared) { self.repository = repository } func loadArticles() async { isLoading = true errorMessage = nil do { articles = try await repository.fetchArticles() } catch { errorMessage = "Impossible de charger les articles" } isLoading = false } } // View (SwiftUI) struct ArticleListView: View { @StateObject private var viewModel = ArticleListViewModel() var body: some View { Group { if viewModel.isLoading { ProgressView() } else { List(viewModel.articles) { article in Text(article.title) } } } .task { await viewModel.loadArticles() } } } ``` ### 19. Expliquez l'injection de dépendances L'injection de dépendances consiste à fournir les dépendances d'un objet de l'extérieur plutôt que de les créer en interne. Cela améliore la testabilité et le découplage. ```swift // DependencyInjection.swift // Protocol pour l'abstraction protocol UserServiceProtocol { func fetchUser(id: Int) async throws -> User } // Implémentation réelle class UserService: UserServiceProtocol { func fetchUser(id: Int) async throws -> User { // Appel API réel let url = URL(string: "https://api.example.com/users/\(id)")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data) } } // ViewModel avec injection class ProfileViewModel: ObservableObject { private let userService: UserServiceProtocol // Injection via le constructeur init(userService: UserServiceProtocol = UserService()) { self.userService = userService } func loadProfile(id: Int) async { // Utilise le service injecté } } // Mock pour les tests class MockUserService: UserServiceProtocol { func fetchUser(id: Int) async throws -> User { return User(id: id, name: "Test User", email: "test@test.com") } } // Dans les tests let viewModel = ProfileViewModel(userService: MockUserService()) ``` ### 20. Comment implémenter le pattern Singleton ? Le Singleton garantit qu'une classe n'a qu'une seule instance globalement accessible. En Swift, on utilise une propriété statique avec un initializer privé. ```swift // Singleton.swift class NetworkManager { // Instance unique accessible globalement static let shared = NetworkManager() // Initializer privé empêche la création d'autres instances private init() { // Configuration initiale } private let session = URLSession.shared func request(_ url: URL) async throws -> T { let (data, _) = try await session.data(from: url) return try JSONDecoder().decode(T.self, from: data) } } // Utilisation let user: User = try await NetworkManager.shared.request(url) ``` > **Prudence avec les Singletons** > > Les Singletons créent un état global qui complique les tests et le découplage. Préférer l'injection de dépendances quand possible. ## Concepts avancés ### 21. Expliquez `@escaping` pour les closures Une closure est `@escaping` quand elle peut être appelée après le retour de la fonction qui la reçoit. C'est courant pour les callbacks asynchrones et le stockage de closures. ```swift // Escaping.swift class DataLoader { // Stockage de completion handlers private var completionHandlers: [() -> Void] = [] // @escaping car la closure est stockée et appelée plus tard func loadData(completion: @escaping () -> Void) { completionHandlers.append(completion) DispatchQueue.global().async { // Travail asynchrone... Thread.sleep(forTimeInterval: 1) DispatchQueue.main.async { // La closure est appelée après le return de loadData completion() } } } // Non-escaping par défaut : closure appelée avant le return func transform(data: Data, using transformer: (Data) -> String) -> String { return transformer(data) // Appelée immédiatement } } // Avec @escaping, attention aux retain cycles class ViewController { var loader = DataLoader() var data: String? func load() { loader.loadData { [weak self] in // [weak self] évite le retain cycle self?.data = "Loaded" } } } ``` ### 22. Qu'est-ce que `@propertyWrapper` ? Les property wrappers encapsulent la logique de stockage et d'accès d'une propriété. Ils permettent de réutiliser des patterns comme la validation, le logging ou la persistence. ```swift // PropertyWrapper.swift // Property wrapper pour valeurs positives uniquement @propertyWrapper struct Positive { private var value: Int = 0 var wrappedValue: Int { get { value } set { value = max(0, newValue) } // Force positif } // Valeur projetée accessible via $ var projectedValue: Bool { value > 0 } init(wrappedValue: Int) { self.wrappedValue = wrappedValue } } // Utilisation struct Player { @Positive var score: Int = 0 @Positive var health: Int = 100 } var player = Player() player.score = -50 // Devient 0 (clamped) print(player.score) // 0 print(player.$score) // false (projectedValue) player.score = 100 print(player.$score) // true ``` ### 23. Expliquez les result builders Les result builders permettent de construire des valeurs complexes avec une syntaxe déclarative. C'est le mécanisme derrière la syntaxe DSL de SwiftUI. ```swift // ResultBuilder.swift // Définition d'un result builder @resultBuilder struct StringBuilder { static func buildBlock(_ components: String...) -> String { components.joined(separator: " ") } static func buildOptional(_ component: String?) -> String { component ?? "" } static func buildEither(first component: String) -> String { component } static func buildEither(second component: String) -> String { component } } // Fonction utilisant le builder func buildGreeting(@StringBuilder _ content: () -> String) -> String { content() } // Utilisation avec syntaxe déclarative let greeting = buildGreeting { "Bonjour" "et" "bienvenue" if Bool.random() { "!" } else { "." } } print(greeting) // "Bonjour et bienvenue !" ou "Bonjour et bienvenue ." ``` ### 24. Comment fonctionne `some` et les opaque types ? `some` déclare un opaque type : le type exact est connu du compilateur mais masqué à l'appelant. C'est essentiel pour les protocols avec associated types et permet des optimisations. ```swift // OpaqueTypes.swift // Sans some : erreur car Collection a un associated type // func makeCollection() -> Collection { ... } // ❌ Erreur // Avec some : le type exact est masqué mais consistant func makeArray() -> some Collection { return [1, 2, 3] // Retourne toujours le même type concret } // Utilisé dans SwiftUI pour body struct ContentView: View { var body: some View { // Type exact inféré mais masqué VStack { Text("Hello") Text("World") } } } // Différence avec any (existential) func processAny(_ collection: any Collection) { // Peut accepter différents types, overhead runtime } func processSome(_ collection: some Collection) { // Type fixé à la compilation, pas d'overhead } ``` ### 25. Expliquez les macros Swift Les macros (Swift 5.9+) génèrent du code à la compilation. Elles réduisent le boilerplate tout en restant type-safe et debuggables. ```swift // Macros.swift // Macro freestanding : génère une expression let (x, y) = #unwrap(optionalX, optionalY) // Expands to: guard let x = optionalX, let y = optionalY else { ... } // Macro attached : modifie une déclaration @Observable // Macro qui génère le boilerplate Observable class UserModel { var name: String = "" var email: String = "" } // Génère automatiquement @ObservationTracked, ObservationRegistrar, etc. // Macro pour Codable avec customisation @Codable struct Product { let id: Int @CodableKey("product_name") let name: String // Renomme la clé JSON @CodableIgnored var cache: Data? // Exclut du codage } // Créer une macro custom @attached(member, names: named(init)) public macro AutoInit() = #externalMacro(module: "MyMacros", type: "AutoInitMacro") @AutoInit struct Point { let x: Int let y: Int // init(x: Int, y: Int) généré automatiquement } ``` > **Debugging des macros** > > Dans Xcode, clic droit sur une macro puis "Expand Macro" pour voir le code généré. Utile pour comprendre et débugger. ## Conclusion Ces 25 questions couvrent les fondamentaux que tout développeur Swift doit maîtriser pour réussir un entretien iOS. De la gestion mémoire avec ARC aux patterns de concurrence modernes, chaque concept s'intègre dans l'écosystème Swift. ### Checklist de révision - ✅ Maîtriser les optionals et leurs différentes méthodes d'unwrapping - ✅ Comprendre la différence entre types valeur et types référence - ✅ Savoir identifier et résoudre les retain cycles avec weak/unowned - ✅ Utiliser async/await et les actors pour la concurrence - ✅ Implémenter les patterns Delegate, MVVM et Injection de dépendances - ✅ Connaître les protocols, génériques et Codable - ✅ Comprendre les concepts avancés : property wrappers, result builders, macros ### Ressources complémentaires Pour approfondir, la documentation officielle Swift reste la référence. La pratique régulière avec des projets personnels et des exercices de code permet d'ancrer ces concepts. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ios/top-25-questions-entretien-swift