décembre 21, 2025
19 min de lecture

Questions d'entretien pour développeur mobile senior (iOS) : Guide complet

interview
career-advice
job-search
Questions d'entretien pour développeur mobile senior (iOS) : Guide complet
MB

Milad Bonakdar

Auteur

Maîtrisez le développement iOS avancé avec des questions d'entretien essentielles couvrant les modèles d'architecture, l'optimisation des performances, la concurrence, Core Data et la conception de systèmes pour les développeurs seniors.


Introduction

On attend des développeurs iOS expérimentés qu'ils conçoivent des applications robustes et évolutives, tout en maintenant une qualité de code et des performances élevées. Ce rôle exige une connaissance approfondie des frameworks iOS, des modèles de conception, de la gestion de la mémoire et la capacité de prendre des décisions architecturales éclairées.

Ce guide complet couvre les questions d'entretien essentielles pour les développeurs iOS expérimentés, couvrant les concepts Swift avancés, les modèles architecturaux, l'optimisation des performances, la concurrence et la conception de systèmes. Chaque question comprend des réponses détaillées, une évaluation de la rareté et des niveaux de difficulté.


Swift Avancé et Fonctionnalités du Langage (6 Questions)

1. Expliquez la gestion de la mémoire de Swift et l'ARC (Automatic Reference Counting).

Réponse: L'ARC gère automatiquement la mémoire en suivant et en gérant les références aux instances de classe.

  • Comment ça marche : Chaque instance de classe a un compteur de références. Lorsque le compteur atteint zéro, l'instance est désallouée.
  • Références Fortes : Par défaut. Augmente le compteur de références.
  • Références Faibles : N'augmentent pas le compteur de références. Deviennent automatiquement nil lorsque l'instance est désallouée.
  • Références Non Possédées : N'augmentent pas le compteur de références mais supposent que l'instance existe toujours.
  • Cycles de Rétention : Se produisent lorsque deux objets détiennent des références fortes l'un vers l'autre, empêchant la désallocation.
class Personne {
    var nom: String
    var appartement: Appartement?
    
    init(nom: String) { self.nom = nom }
    deinit { print("\(nom) est en cours de désallocation") }
}

class Appartement {
    var unité: String
    weak var locataire: Personne?  // faible pour briser le cycle de rétention
    
    init(unité: String) { self.unité = unité }
    deinit { print("Appartement \(unité) est en cours de désallocation") }
}

Rareté : Très Courant Difficulté : Difficile


2. Que sont les Génériques en Swift et pourquoi sont-ils utiles ?

Réponse : Les génériques vous permettent d'écrire des fonctions et des types flexibles et réutilisables qui peuvent fonctionner avec n'importe quel type.

  • Avantages : Réutilisabilité du code, sécurité des types, performances (pas de surcharge d'exécution)
  • Contraintes de Type : Restreindre les types génériques à des protocoles ou des classes spécifiques
  • Types Associés : Utilisés dans les protocoles pour définir des types d'espace réservé
// Fonction générique
func echange<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Type générique
struct Pile<Element> {
    private var éléments: [Element] = []
    
    mutating func push(_ élément: Element) {
        éléments.append(élément)
    }
    
    mutating func pop() -> Element? {
        return éléments.popLast()
    }
}

// Contrainte de type
func trouveIndex<T: Equatable>(de valeurÀTrouver: T, dans tableau: [T]) -> Int? {
    for (index, valeur) in tableau.enumerated() {
        if valeur == valeurÀTrouver {
            return index
        }
    }
    return nil
}

Rareté : Courant Difficulté : Moyen


3. Expliquez la différence entre les fermetures escaping et non-escaping.

Réponse :

  • Non-escaping (par défaut) : La fermeture est exécutée avant que la fonction ne retourne. Le compilateur peut mieux optimiser.
  • Escaping (@escaping) : La fermeture survit à la fonction (stockée dans une propriété, appelée de manière asynchrone). Doit explicitement capturer self.
class GestionnaireReseau {
    var gestionnairesAchèvement: [() -> Void] = []
    
    // Fermeture escaping - stockée pour plus tard
    func récupèreDonnées(achèvement: @escaping (Data?) -> Void) {
        URLSession.shared.dataTask(with: url) { données, _, _ in
            achèvement(données)  // Appelée après que la fonction retourne
        }.resume()
    }
    
    // Fermeture non-escaping - exécutée immédiatement
    func traiteDonnées(_ données: Data, transformation: (Data) -> String) -> String {
        return transformation(données)  // Appelée avant que la fonction retourne
    }
}

Rareté : Courant Difficulté : Moyen


4. Quelle est la différence entre map, flatMap et compactMap ?

Réponse : Ce sont des fonctions d'ordre supérieur pour transformer des collections :

  • map : Transforme chaque élément et retourne un tableau de résultats
  • compactMap : Comme map mais filtre les valeurs nil
  • flatMap : Aplatit les tableaux imbriqués en un seul tableau
let nombres = [1, 2, 3, 4, 5]

// map
let doublés = nombres.map { $0 * 2 }
// [2, 4, 6, 8, 10]

// compactMap - supprime nil
let chaînes = ["1", "2", "trois", "4"]
let nombresValides = chaînes.compactMap { Int($0) }
// [1, 2, 4]

// flatMap - aplatit les tableaux
let imbriqués = [[1, 2], [3, 4], [5]]
let aplatis = imbriqués.flatMap { $0 }
// [1, 2, 3, 4, 5]

Rareté : Courant Difficulté : Facile


5. Expliquez les Property Wrappers en Swift.

Réponse : Les property wrappers ajoutent une couche de séparation entre le code qui gère la façon dont une propriété est stockée et le code qui définit une propriété.

  • Exemples intégrés : @State, @Published, @AppStorage dans SwiftUI
  • Wrappers personnalisés : Définir des comportements de propriété réutilisables
@propertyWrapper
struct Capitalisé {
    private var valeur: String = ""
    
    var wrappedValue: String {
        get { valeur }
        set { valeur = newValue.capitalized }
    }
}

struct Utilisateur {
    @Capitalisé var prénom: String
    @Capitalisé var nom: String
}

var utilisateur = Utilisateur()
utilisateur.prénom = "jean"
print(utilisateur.prénom)  // "Jean"

// Wrapper UserDefaults
@propertyWrapper
struct UserDefault<T> {
    let clé: String
    let valeurParDéfaut: T
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: clé) as? T ?? valeurParDéfaut }
        set { UserDefaults.standard.set(newValue, forKey: clé) }
    }
}

struct Paramètres {
    @UserDefault(clé: "nomUtilisateur", valeurParDéfaut: "Invité")
    static var nomUtilisateur: String
}

Rareté : Moyen Difficulté : Difficile


6. Qu'est-ce que le type Result et comment est-il utilisé ?

Réponse : Result est une énumération qui représente soit le succès, soit l'échec, rendant la gestion des erreurs plus explicite.

  • Définition : enum Result<Success, Failure: Error>
  • Avantages : Gestion des erreurs avec sécurité des types, contrats d'API plus clairs, mieux que les fonctions de lancement pour le code asynchrone
enum ErreurReseau: Error {
    case URLInvalide
    case pasDeDonnées
    case erreurDécodage
}

func récupèreUtilisateur(id: Int, achèvement: @escaping (Result<Utilisateur, ErreurReseau>) -> Void) {
    guard let url = URL(string: "https://api.example.com/utilisateurs/\(id)") else {
        achèvement(.failure(.URLInvalide))
        return
    }
    
    URLSession.shared.dataTask(with: url) { données, réponse, erreur in
        guard let données = données else {
            achèvement(.failure(.pasDeDonnées))
            return
        }
        
        do {
            let utilisateur = try JSONDecoder().decode(Utilisateur.self, from: données)
            achèvement(.success(utilisateur))
        } catch {
            achèvement(.failure(.erreurDécodage))
        }
    }.resume()
}

// Utilisation
récupèreUtilisateur(id: 1) { résultat in
    switch résultat {
    case .success(let utilisateur):
        print("Utilisateur : \(utilisateur.nom)")
    case .failure(let erreur):
        print("Erreur : \(erreur)")
    }
}

Rareté : Courant Difficulté : Moyen


Modèles d'Architecture (5 Questions)

7. Expliquez le modèle MVVM (Model-View-ViewModel).

Réponse : MVVM sépare la logique de l'interface utilisateur de la logique métier, rendant le code plus testable et maintenable.

Loading diagram...
  • Model : Données et logique métier
  • View : Interface utilisateur (UIViewController, SwiftUI View)
  • ViewModel : Logique de présentation, transforme les données du modèle pour la vue
  • Avantages : Testable (ViewModel n'a pas de dépendances d'interface utilisateur), ViewModels réutilisables, séparation claire des préoccupations
// Model
struct Utilisateur {
    let id: Int
    let prénom: String
    let nom: String
}

// ViewModel
class UtilisateurViewModel {
    private let utilisateur: Utilisateur
    
    var nomComplet: String {
        "\(utilisateur.prénom) \(utilisateur.nom)"
    }
    
    var texteAffiché: String {
        "Utilisateur #\(utilisateur.id) : \(nomComplet)"
    }
    
    init(utilisateur: Utilisateur) {
        self.utilisateur = utilisateur
    }
}

// View
class UtilisateurViewController: UIViewController {
    private let viewModel: UtilisateurViewModel
    private let étiquette = UILabel()
    
    init(viewModel: UtilisateurViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        étiquette.text = viewModel.texteAffiché
    }
}

Rareté : Très Courant Difficulté : Moyen


8. Qu'est-ce que le modèle Coordinator et pourquoi l'utiliser ?

Réponse : Le modèle Coordinator sépare la logique de navigation des view controllers.

  • Problème : View Controllers massifs avec la logique de navigation mélangée à la logique de l'interface utilisateur
  • Solution : Les Coordinators gèrent le flux de navigation
  • Avantages : View controllers réutilisables, navigation testable, flux d'application clair
protocol Coordinator {
    var navigationController: UINavigationController { get }
    func start()
}

class AppCoordinator: Coordinator {
    let navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        afficheConnexion()
    }
    
    func afficheConnexion() {
        let connexionVC = ConnexionViewController()
        connexionVC.coordinator = self
        navigationController.pushViewController(connexionVC, animated: true)
    }
    
    func afficheAccueil() {
        let accueilVC = AccueilViewController()
        accueilVC.coordinator = self
        navigationController.pushViewController(accueilVC, animated: true)
    }
}

Rareté : Moyen Difficulté : Difficile


9. Expliquez l'Injection de Dépendances dans iOS.

Réponse : L'Injection de Dépendances est un modèle de conception où les dépendances sont fournies à un objet plutôt que créées en interne.

  • Avantages : Testabilité (injecter des mocks), flexibilité, couplage lâche
  • Types :
    • Injection de Constructeur : Passer les dépendances via l'initialiseur (le plus courant)
    • Injection de Propriété : Définir les dépendances après l'initialisation
    • Injection de Méthode : Passer les dépendances comme paramètres de méthode
// Protocole pour l'abstraction
protocol ServiceReseau {
    func récupèreDonnées(achèvement: @escaping (Result<Data, Error>) -> Void)
}

// Implémentation concrète
class URLSessionServiceReseau: ServiceReseau {
    func récupèreDonnées(achèvement: @escaping (Result<Data, Error>) -> Void) {
        // Vrai appel réseau
    }
}

// Mock pour les tests
class MockServiceReseau: ServiceReseau {
    func récupèreDonnées(achèvement: @escaping (Result<Data, Error>) -> Void) {
        achèvement(.success(Data()))
    }
}

// ViewModel avec injection de dépendances
class UtilisateurViewModel {
    private let serviceReseau: ServiceReseau
    
    // Injection de constructeur
    init(serviceReseau: ServiceReseau) {
        self.serviceReseau = serviceReseau
    }
    
    func chargeUtilisateurs() {
        serviceReseau.récupèreDonnées { résultat in
            // Gérer le résultat
        }
    }
}

// Utilisation
let viewModel = UtilisateurViewModel(serviceReseau: URLSessionServiceReseau())

// Tests
let viewModelTest = UtilisateurViewModel(serviceReseau: MockServiceReseau())

Rareté : Courant Difficulté : Moyen


10. Qu'est-ce que le modèle Repository ?

Réponse : Le modèle Repository abstrait la logique d'accès aux données, fournissant une API propre pour les opérations de données.

  • Avantages : Logique de données centralisée, facile à changer de sources de données (API, base de données, cache), testable
  • Implémentation : Le Repository coordonne entre plusieurs sources de données
protocol UtilisateurRepository {
    func récupèreUtilisateur(id: Int) async throws -> Utilisateur
    func sauvegardeUtilisateur(_ utilisateur: Utilisateur) async throws
}

class UtilisateurRepositoryImpl: UtilisateurRepository {
    private let serviceAPI: APIService
    private let serviceCache: CacheService
    
    init(serviceAPI: APIService, serviceCache: CacheService) {
        self.serviceAPI = serviceAPI
        self.serviceCache = serviceCache
    }
    
    func récupèreUtilisateur(id: Int) async throws -> Utilisateur {
        // Vérifier le cache en premier
        if let utilisateurMisEnCache = try? await serviceCache.récupèreUtilisateur(id: id) {
            return utilisateurMisEnCache
        }
        
        // Récupérer de l'API
        let utilisateur = try await serviceAPI.récupèreUtilisateur(id: id)
        
        // Mettre à jour le cache
        try? await serviceCache.sauvegardeUtilisateur(utilisateur)
        
        return utilisateur
    }
    
    func sauvegardeUtilisateur(_ utilisateur: Utilisateur) async throws {
        try await serviceAPI.metÀJourUtilisateur(utilisateur)
        try await serviceCache.sauvegardeUtilisateur(utilisateur)
    }
}

Rareté : Moyen Difficulté : Moyen


11. Expliquez les différences entre MVC, MVP et MVVM.

Réponse :

  • MVC (Model-View-Controller) :
    • Modèle par défaut d'Apple
    • Le Controller sert d'intermédiaire entre le Model et la View
    • Problème : View Controllers massifs
  • MVP (Model-View-Presenter) :
    • Le Presenter gère toute la logique de l'interface utilisateur
    • La View est passive (affiche juste les données)
    • Meilleure testabilité que MVC
  • MVVM (Model-View-ViewModel) :
    • Le ViewModel expose les flux de données
    • La View se lie au ViewModel
    • Idéal pour la programmation réactive (Combine, RxSwift)

Rareté : Courant Difficulté : Difficile


Performance et Optimisation (5 Questions)

12. Comment optimisez-vous les performances des table views et des collection views ?

Réponse : Plusieurs stratégies améliorent les performances de défilement :

  1. Réutilisation des Cellules : Utilisez dequeueReusableCell correctement
  2. Évitez les Opérations Lourdes : N'effectuez pas de calculs coûteux dans cellForRowAt
  3. Optimisation des Images :
    • Redimensionnez les images à la taille d'affichage
    • Utilisez des threads d'arrière-plan pour le traitement des images
    • Mettez en cache les images décodées
  4. Prérécupération : Implémentez UITableViewDataSourcePrefetching
  5. Mise en Cache de la Hauteur : Mettez en cache les hauteurs de cellule calculées
  6. Évitez la Transparence : Les vues opaques rendent plus rapidement
class ImageTableViewCell: UITableViewCell {
    static let identifiant = "ImageCell"
    private let customImageView = UIImageView()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        customImageView.image = nil  // Effacer l'ancienne image
    }
}

// Dans le view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellule = tableView.dequeueReusableCell(withIdentifier: ImageTableViewCell.identifiant, for: indexPath) as! ImageTableViewCell
    
    // Charger l'image de manière asynchrone
    DispatchQueue.global(qos: .userInitiated).async {
        let image = self.chargeEtRedimensionneImage(at: indexPath)
        DispatchQueue.main.async {
            cellule.imageView?.image = image
        }
    }
    
    return cellule
}

Rareté : Très Courant Difficulté : Moyen


13. Expliquez Instruments et comment vous l'utilisez pour le profilage des performances.

Réponse : Instruments est l'outil d'analyse des performances de Xcode.

  • Instruments Courants :
    • Time Profiler : Identifie le code gourmand en CPU
    • Allocations : Suit les allocations de mémoire et les fuites
    • Leaks : Détecte les fuites de mémoire
    • Network : Surveille l'activité réseau
    • Energy Log : Analyse l'utilisation de la batterie
  • Flux de Travail :
    1. Profiler l'application (Cmd+I)
    2. Choisir un instrument
    3. Enregistrer et interagir avec l'application
    4. Analyser l'arborescence des appels et la chronologie
    5. Identifier les goulots d'étranglement

Rareté : Courant Difficulté : Moyen


14. Comment détectez-vous et corrigez-vous les fuites de mémoire ?

Réponse : Les fuites de mémoire se produisent lorsque les objets ne sont pas désalloués lorsqu'ils ne sont plus nécessaires.

  • Causes Courantes :
    • Cycles de rétention (cycles de références fortes)
    • Fermetures capturant self fortement
    • Délégués non marqués comme weak
  • Détection :
    • Outil Instruments Leaks
    • Débogueur de graphe de mémoire dans Xcode
    • Surveiller l'augmentation de l'utilisation de la mémoire
  • Corrections :
    • Utilisez weak ou unowned pour les délégués
    • Utilisez [weak self] ou [unowned self] dans les fermetures
    • Brisez les cycles de rétention
class ViewController: UIViewController {
    var fermeture: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // MAUVAIS - cycle de rétention
        fermeture = {
            self.view.backgroundColor = .red
        }
        
        // BON - weak self
        fermeture = { [weak self] in
            self?.view.backgroundColor = .red
        }
        
        // BON - unowned self (si self existe toujours)
        fermeture = { [unowned self] in
            self.view.backgroundColor = .red
        }
    }
}

Rareté : Très Courant Difficulté : Moyen


15. Quelles techniques utilisez-vous pour l'optimisation du démarrage de l'application ?

Réponse : Un lancement d'application plus rapide améliore l'expérience utilisateur :

  1. Chargement Paresseux : Initialisez les objets uniquement lorsque cela est nécessaire
  2. Réduire le Chargement de Dylib : Minimisez les bibliothèques dynamiques
  3. Optimiser application:didFinishLaunching :
    • Déplacez le travail non critique en arrière-plan
    • Différez l'initialisation lourde
  4. Taille du Binaire : Un binaire plus petit se charge plus rapidement
  5. Évitez les Opérations Lourdes : Ne bloquez pas le thread principal
  6. Mesurer : Utilisez le modèle de lancement d'application d'Instruments
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Critique : Configurer la fenêtre et le view controller racine
    configureFenêtre()
    
    // Différer le travail non critique
    DispatchQueue.main.async {
        self.configureAnalyse()
        self.configureRapportsDeCrash()
    }
    
    // Travail en arrière-plan
    DispatchQueue.global(qos: .background).async {
        self.préchargeDonnées()
    }
    
    return true
}

Rareté : Courant Difficulté : Moyen


16. Comment gérez-vous la mise en cache et le chargement des images ?

Réponse : Une gestion efficace des images est cruciale pour les performances :

  • Stratégies :
    • Cache Mémoire : Accès rapide, taille limitée
    • Cache Disque : Persistant, plus grande capacité
    • Téléchargement : Récupérer du réseau
  • Bibliothèques : SDWebImage, Kingfisher (gèrent la mise en cache automatiquement)
  • Implémentation Personnalisée :
class CacheImage {
    static let partagé = CacheImage()
    
    private let cacheMémoire = NSCache<NSString, UIImage>()
    private let gestionnaireFichiers = FileManager.default
    private let répertoireCache: URL
    
    private init() {
        répertoireCache = gestionnaireFichiers.urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("CacheImage")
        try? gestionnaireFichiers.createDirectory(at: répertoireCache, withIntermediateDirectories: true)
    }
    
    func chargeImage(depuis url: URL, achèvement: @escaping (UIImage?) -> Void) {
        let clé = url.absoluteString as NSString
        
        // Vérifier le cache mémoire
        if let imageMiseEnCache = cacheMémoire.object(forKey: clé) {
            achèvement(imageMiseEnCache)
            return
        }
        
        // Vérifier le cache disque
        let URLFichier = répertoireCache.appendingPathComponent(url.lastPathComponent)
        if let imageDisque = UIImage(contentsOfFile: URLFichier.path) {
            cacheMémoire.setObject(imageDisque, forKey: clé)
            achèvement(imageDisque)
            return
        }
        
        // Télécharger
        URLSession.shared.dataTask(with: url) { données, _, _ in
            guard let données = données, let image = UIImage(data: données) else {
                achèvement(nil)
                return
            }
            
            // Sauvegarder dans les caches
            self.cacheMémoire.setObject(image, forKey: clé)
            try? données.write(to: URLFichier)
            
            DispatchQueue.main.async {
                achèvement(image)
            }
        }.resume()
    }
}

Rareté : Courant Difficulté : Difficile


Concurrence et Programmation Asynchrone (4 Questions)

17. Expliquez async/await en Swift.

Réponse : Le modèle de concurrence moderne de Swift introduit dans Swift 5.5.

  • Avantages : Syntaxe plus propre que les gestionnaires d'achèvement, gestion des erreurs plus facile, sécurité des threads appliquée par le compilateur
  • Mots-clés :
    • async : Marque une fonction qui peut suspendre
    • await : Marque un point de suspension
    • Task : Crée un nouveau contexte asynchrone
    • actor : Type de référence thread-safe
// Ancienne méthode avec les gestionnaires d'achèvement
func récupèreUtilisateur(id: Int, achèvement: @escaping (Result<Utilisateur, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { données, réponse, erreur in
        // Gérer le résultat
    }.resume()
}

// Nouvelle méthode avec async/await
func récupèreUtilisateur(id: Int) async throws -> Utilisateur {
    let (données, _) = try await URLSession.shared.data(from: url)
    let utilisateur = try JSONDecoder().decode(Utilisateur.self, from: données)
    return utilisateur
}

// Utilisation
Task {
    do {
        let utilisateur = try await récupèreUtilisateur(id: 1)
        print(utilisateur.nom)
    } catch {
        print("Erreur : \(erreur)")
    }
}

// Exécution parallèle
async let utilisateur1 = récupèreUtilisateur(id: 1)
async let utilisateur2 = récupèreUtilisateur(id: 2)
let utilisateurs = try await [utilisateur1, utilisateur2]

Rareté : Très Courant Difficulté : Difficile


18. Que sont les Actors en Swift ?

Réponse : Les actors sont des types de référence qui protègent leur état mutable contre les courses de données.

  • Sécurité des Threads : Une seule tâche peut accéder à l'état mutable d'un actor à la fois
  • Synchronisation Automatique : Le compilateur applique un accès sûr
  • Main Actor : Actor spécial pour les mises à jour de l'interface utilisateur
actor CompteBancaire {
    private var solde: Double = 0
    
    func déposer(montant: Double) {
        solde += montant
    }
    
    func retirer(montant: Double) -> Bool {
        guard solde >= montant else { return false }
        solde -= montant
        return true
    }
    
    func obtenirSolde() -> Double {
        return solde
    }
}

// Utilisation
let compte = CompteBancaire()

Task {
    await compte.déposer(montant: 100)
    let solde = await compte.obtenirSolde()
    print("Solde : \(solde)")
}

// Main Actor pour les mises à jour de l'interface utilisateur
@MainActor
class ViewModel: ObservableObject {
    @Published var données: [String] = []
    
    func chargeDonnées() async {
        let nouvellesDonnées = await récupèreDonnéesDeLAPI()
        // Automatiquement sur le thread principal
        self.données = nouvellesDonnées
    }
}

Rareté : Moyen Difficulté : Difficile


19. Expliquez le framework Combine.

Réponse : Combine est le framework de programmation réactive d'Apple.

  • Concepts Clés :
    • Publisher : Émet des valeurs au fil du temps
    • Subscriber : Reçoit des valeurs
    • Operator : Transforme les valeurs
  • Avantages : Déclaratif, composable, opérateurs intégrés
  • Cas d'Utilisation : Mise en réseau, gestion des entrées utilisateur, liaison de données
import Combine

class UtilisateurViewModel: ObservableObject {
    @Published var texteRecherche = ""
    @Published var utilisateurs: [Utilisateur] = []
    
    private var annulations = Set<AnyCancellable>()
    
    init() {
        $texteRecherche
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .filter { !$0.isEmpty }
            .flatMap { requête in
                self.rechercheUtilisateurs(requête: requête)
            }
            .receive(on: DispatchQueue.main)
            .sink { [weak self] utilisateurs in
                self?.utilisateurs = utilisateurs
            }
            .store(in: &annulations)
    }
    
    func rechercheUtilisateurs(requête: String) -> AnyPublisher<[Utilisateur], Never> {
        // Retourner le publisher
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [Utilisateur].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .eraseToAnyPublisher()
    }
}

Rareté : Courant Difficulté : Difficile


20. Quelle est la différence entre les files d'attente Serial et Concurrent ?

Réponse : Les files d'attente de dispatch exécutent les tâches en série ou en concurrence :

  • File d'Attente Serial : Exécute une tâche à la fois dans l'ordre FIFO. Les tâches attendent que la tâche précédente se termine.
  • File d'Attente Concurrent : Exécute plusieurs tâches simultanément. Les tâches commencent dans l'ordre FIFO mais peuvent se terminer dans n'importe quel ordre.
  • File d'Attente Principale : File d'attente serial spéciale pour les mises à jour de l'interface utilisateur
// File d'attente serial
let fileAttenteSerial = DispatchQueue(label: "com.app.serial")
fileAttenteSerial.async { print("Tâche 1") }
fileAttenteSerial.async { print("Tâche 2") }
// Sortie : Tâche 1, Tâche 2 (toujours dans l'ordre)

// File d'attente concurrent
let fileAttenteConcurrent = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
fileAttenteConcurrent.async { print("Tâche A") }
fileAttenteConcurrent.async { print("Tâche B") }
// Sortie : Tâche A, Tâche B OU Tâche B, Tâche A (ordre non garanti)

// Barrière pour les opérations d'écriture
fileAttenteConcurrent.async(flags: .barrier) {
    // Accès exclusif - aucune autre tâche ne s'exécute simultanément
    print("Opération d'écriture")
}

Rareté : Courant Difficulté : Moyen


Core Data et Persistance (3 Questions)

21. Expliquez l'architecture de Core Data et ses principaux composants.

Réponse : Core Data est le framework de graphe d'objets et de persistance d'Apple.

Loading diagram...
  • NSManagedObjectModel : Définition du schéma (entités, attributs, relations)
  • NSPersistentStoreCoordinator : Coordonne entre le contexte et le magasin
  • NSManagedObjectContext : Mémoire de travail pour les objets (comme un bloc-notes)
  • NSPersistentStore : Stockage réel (SQLite, binaire, en mémoire)

Rareté : Courant Difficulté : Moyen


22. Comment gérez-vous la concurrence dans Core Data ?

Réponse : Les contextes Core Data ne sont pas thread-safe. Utilisez des modèles de concurrence appropriés :

  • Types de Contexte :
    • Contexte de File d'Attente Principale : Pour les opérations de l'interface utilisateur
    • Contexte de File d'Attente Privée : Pour les opérations en arrière-plan
  • Meilleures Pratiques :
    • Ne jamais passer d'objets gérés entre les threads
    • Utilisez perform ou performAndWait pour les opérations de contexte
    • Passez les IDs d'objets entre les contextes
class CoreDataManager {
    let conteneurPersistant: NSPersistentContainer
    
    var contexteVue: NSManagedObjectContext {
        return conteneurPersistant.viewContext
    }
    
    func effectueTâcheEnArrièrePlan(_ block: @escaping (NSManagedObjectContext) -> Void) {
        conteneurPersistant.performBackgroundTask { contexte in
            block(contexte)
            
            if contexte.hasChanges {
                try? contexte.save()
            }
        }
    }
}

// Utilisation
coreDataManager.effectueTâcheEnArrièrePlan { contexte in
    let utilisateur = Utilisateur(context: contexte)
    utilisateur.nom = "Jean"
    // Le contexte sauvegarde automatiquement
}

Rareté : Moyen Difficulté : Difficile


23. Qu'est-ce que NSFetchedResultsController et quand l'utilisez-vous ?

Réponse : NSFetchedResultsController gère efficacement les résultats Core Data pour les table/collection views.

  • **Avantages
Newsletter subscription

Conseils de carrière hebdomadaires qui fonctionnent vraiment

Recevez les dernières idées directement dans votre boîte de réception

Decorative doodle

Votre Prochain Entretien n'est qu'à un CV

Créez un CV professionnel et optimisé en quelques minutes. Aucune compétence en design nécessaire—juste des résultats prouvés.

Créer mon CV

Partager cet article

Soyez Embauché 50% Plus Rapidement

Les chercheurs d'emploi utilisant des CV professionnels améliorés par l'IA décrochent des postes en moyenne en 5 semaines contre 10 normalement. Arrêtez d'attendre et commencez à passer des entretiens.