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.

Questions d'entretien Swift pour développeurs iOS

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.

Constantes.swiftswift
// 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.

Optionals.swiftswift
// 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.

ValueVsReference.swiftswift
// 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.

PatternMatching.swiftswift
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.

Closures.swiftswift
// 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.

ARC.swiftswift
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.

RetainCycle.swiftswift
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.

WeakVsUnowned.swiftswift
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.

Prêt à réussir tes entretiens iOS ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

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.

Protocols.swiftswift
// 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.

AssociatedTypes.swiftswift
// 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.

Generics.swiftswift
// Fonction générique
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Contrainte de type avec where
func findIndex<T: Equatable>(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<Element> {
    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<Int>()
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.

Codable.swiftswift
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.

AsyncAwait.swiftswift
// 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.

Actors.swiftswift
// 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.

TaskGroup.swiftswift
// 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.

MainActor.swiftswift
// 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.).

DelegatePattern.swiftswift
// 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.

MVVM.swiftswift
// 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() }
    }
}

Prêt à réussir tes entretiens iOS ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

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.

DependencyInjection.swiftswift
// 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é.

Singleton.swiftswift
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<T: Decodable>(_ 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.

Escaping.swiftswift
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.

PropertyWrapper.swiftswift
// 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.

ResultBuilder.swiftswift
// 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.

OpaqueTypes.swiftswift
// 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.

Macros.swiftswift
// 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.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#swift
#ios
#entretien
#interview
#apple

Partager

Articles similaires