décembre 21, 2025
14 min de lecture

Questions d’entretien développeur iOS junior : Swift, UIKit, SwiftUI

interview
career-advice
job-search
entry-level
Questions d’entretien développeur iOS junior : Swift, UIKit, SwiftUI
Milad Bonakdar

Milad Bonakdar

Auteur

Entraînez-vous aux questions d’entretien iOS junior sur Swift, UIKit, SwiftUI, le réseau, la persistance et l’asynchrone, avec des réponses utiles.


Introduction

Pour un entretien de développeur iOS junior, attendez-vous à expliquer les bases de Swift, construire une UI simple avec UIKit ou SwiftUI, récupérer et décoder des données, choisir une persistance adaptée et éviter les erreurs courantes de mémoire ou de threading. Les bonnes réponses sont courtes, concrètes et liées à la façon dont vous construiriez une petite app.

Utilisez ce guide pour vous entraîner aux questions les plus probables pour un premier poste iOS : ce que signifie le concept, quand l’utiliser et quel risque un profil junior doit surveiller.


Fondamentaux de Swift (6 Questions)

1. Quelle est la différence entre var et let en Swift ?

Réponse :

  • var : Déclare une variable mutable. Sa valeur peut être modifiée après l'initialisation.
  • let : Déclare une constante immuable. Une fois définie, sa valeur ne peut plus être modifiée.
  • Bonne pratique : Utilisez let par défaut pour la sécurité et la clarté. N'utilisez var que lorsque vous savez que la valeur changera.
let name = "John"  // Ne peut pas être changé
var age = 25       // Peut être changé
age = 26           // Valide
// name = "Jane"   // Erreur : Impossible d'assigner une valeur

Rareté : Très courant Difficulté : Facile


2. Expliquez les Optionnels en Swift. Qu'est-ce que le "Optional Binding" ?

Réponse : Les optionnels représentent une valeur qui peut être nil (absence de valeur).

  • Déclaration : Utilisez ? après le type : var name: String?
  • Méthodes de déballage :
    • Déballage forcé : name! (dangereux, plante si nil)
    • Optional Binding : Déballage sécurisé en utilisant if let ou guard let
    • Nil Coalescing : name ?? "Default"
    • Optional Chaining : user?.address?.city
var email: String? = "[email protected]"

// Optional Binding
if let unwrappedEmail = email {
    print("Email: \(unwrappedEmail)")
} else {
    print("Pas d'email")
}

// Instruction Guard
guard let email = email else { return }
print(email)

Rareté : Très courant Difficulté : Facile


3. Quelle est la différence entre une class et une struct en Swift ?

Réponse :

  • Class : Type de référence (stocké sur le tas). Prend en charge l'héritage. Passé par référence.
  • Struct : Type de valeur (stocké sur la pile). Pas d'héritage. Passé par copie.
  • Quand utiliser :
    • Struct : Pour les modèles de données simples, lorsque vous voulez une sémantique de valeur (copies indépendantes)
    • Class : Lorsque vous avez besoin d'héritage, de sémantique de référence ou de désinitialiseurs
struct Point {
    var x: Int
    var y: Int
}

class Person {
    var name: String
    init(name: String) { self.name = name }
}

var p1 = Point(x: 0, y: 0)
var p2 = p1  // Copie créée
p2.x = 10    // p1.x est toujours 0

var person1 = Person(name: "John")
var person2 = person1  // Même référence
person2.name = "Jane"  // person1.name est aussi "Jane"

Rareté : Très courant Difficulté : Moyenne


4. Que sont les Closures en Swift ?

Réponse : Les closures sont des blocs de fonctionnalités autonomes qui peuvent être transmis et utilisés dans votre code. Elles sont similaires aux lambdas ou aux fonctions anonymes dans d'autres langages.

  • Syntaxe : { (parameters) -> ReturnType in statements }
  • Capture de valeurs : Les closures peuvent capturer et stocker des références aux variables de leur contexte environnant.
  • Utilisation courante : Gestionnaires de complétion, rappels, opérations de tableau.
// Closure simple
let greet = { (name: String) -> String in
    return "Bonjour, \(name) !"
}
print(greet("Alice"))

// Syntaxe de closure de fuite
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)  // [2, 4, 6, 8, 10]

Rareté : Très courant Difficulté : Moyenne


5. Expliquez la différence entre les références weak et unowned.

Réponse : Les deux sont utilisés pour empêcher les cycles de rétention (fuites de mémoire) dans les types de référence.

  • weak : Référence optionnelle qui devient automatiquement nil lorsque l'objet référencé est désalloué. Utilisez-la lorsque la référence pourrait survivre à l'objet.
  • unowned : Référence non optionnelle qui suppose que l'objet existera toujours. Plante si elle est accédée après la désallocation. Utilisez-la lorsque vous êtes certain que la référence ne survivra pas à l'objet.
class Person {
    var name: String
    weak var apartment: Apartment?  // weak pour empêcher le cycle de rétention
    init(name: String) { self.name = name }
}

class Apartment {
    var unit: String
    unowned let tenant: Person  // unowned - l'appartement a toujours un locataire
    init(unit: String, tenant: Person) {
        self.unit = unit
        self.tenant = tenant
    }
}

Rareté : Courant Difficulté : Moyenne


6. Que sont les Protocoles en Swift ?

Réponse : Les protocoles définissent un modèle de méthodes, de propriétés et d'autres exigences qui conviennent à une tâche ou à une fonctionnalité particulière. Les classes, les structures et les énumérations peuvent adopter des protocoles.

  • Similaire à : Les interfaces dans d'autres langages
  • Extensions de protocole : Peut fournir des implémentations par défaut
  • Programmation orientée protocole : Le paradigme préféré de Swift
protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    var radius: Double
    
    func draw() {
        print("Dessin d'un cercle avec un rayon de \(radius)")
    }
}

struct Rectangle: Drawable {
    var width: Double
    var height: Double
    
    func draw() {
        print("Dessin d'un rectangle de \(width)x\(height)")
    }
}

Rareté : Très courant Difficulté : Facile


Bases de UIKit (5 Questions)

7. Quelle est la différence entre UIView et UIViewController ?

Réponse :

  • UIView : Représente une zone rectangulaire à l'écran. Gère le dessin et la gestion des événements. Exemples : UILabel, UIButton, UIImageView.
  • UIViewController : Gère une hiérarchie de vues. Gère le cycle de vie de la vue, les interactions de l'utilisateur et la navigation. Contient une vue racine et gère ses sous-vues.
  • Relation : Un contrôleur de vue gère les vues. Un contrôleur de vue peut avoir de nombreuses vues.

Rareté : Très courant Difficulté : Facile


8. Expliquez les méthodes du cycle de vie du contrôleur de vue.

Réponse : Les contrôleurs de vue ont des méthodes de cycle de vie spécifiques appelées dans l'ordre :

  1. viewDidLoad() : Appelé une fois lorsque la vue est chargée en mémoire. Configuration qui doit avoir lieu une seule fois.
  2. viewWillAppear(_:) : Appelé avant que la vue n'apparaisse à l'écran. Peut être appelé plusieurs fois.
  3. viewDidAppear(_:) : Appelé après que la vue est apparue à l'écran. Démarrez les animations ici.
  4. viewWillDisappear(_:) : Appelé avant que la vue ne disparaisse.
  5. viewDidDisappear(_:) : Appelé après que la vue a disparu. Nettoyez les ressources.
override func viewDidLoad() {
    super.viewDidLoad()
    // Configuration unique : configurer l'interface utilisateur, charger les données
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // Actualiser les données, commencer à écouter les notifications
}

Rareté : Très courant Difficulté : Facile


9. Qu'est-ce que l'Auto Layout et comment utilisez-vous les contraintes ?

Réponse : L'Auto Layout est un système de mise en page basé sur des contraintes qui vous permet de créer des interfaces utilisateur adaptatives qui fonctionnent sur différentes tailles d'écran et orientations.

  • Contraintes : Définissent les relations entre les vues (largeur, hauteur, espacement, alignement)
  • Méthodes :
    • Interface Builder : Éditeur visuel dans Xcode
    • Programmatic : API NSLayoutConstraint ou NSLayoutAnchor
    • Visual Format Language : Basé sur des chaînes (moins courant maintenant)
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)

NSLayoutConstraint.activate([
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    button.widthAnchor.constraint(equalToConstant: 200),
    button.heightAnchor.constraint(equalToConstant: 50)
])

Rareté : Très courant Difficulté : Moyenne


10. Quelle est la différence entre frame et bounds ?

Réponse :

  • frame : L'emplacement et la taille de la vue dans le système de coordonnées de sa supervue. Utilisé pour positionner la vue.
  • bounds : L'emplacement et la taille de la vue dans son propre système de coordonnées. L'origine est généralement (0, 0). Utilisé pour dessiner du contenu à l'intérieur de la vue.
  • Différence clé : frame est relatif au parent, bounds est relatif à lui-même.
let view = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 150))
print(view.frame)   // (50, 100, 200, 150) - position dans la supervue
print(view.bounds)  // (0, 0, 200, 150) - propre système de coordonnées

Rareté : Courant Difficulté : Moyenne


11. Comment gérez-vous la saisie de l'utilisateur à partir d'un UITextField ?

Réponse : Plusieurs approches existent :

  • Target-Action : Ajouter une cible pour l'événement .editingChanged
  • Delegate : Se conformer au protocole UITextFieldDelegate
  • Combine : Utiliser des éditeurs (approche moderne)
class ViewController: UIViewController, UITextFieldDelegate {
    let textField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Approche Delegate
        textField.delegate = self
        
        // Approche Target-Action
        textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
    }
    
    @objc func textChanged(_ textField: UITextField) {
        print("Texte : \(textField.text ?? "")")
    }
    
    // Méthode Delegate
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

Rareté : Très courant Difficulté : Facile


Bases de SwiftUI (4 Questions)

12. Qu’est-ce que SwiftUI et en quoi est-il différent de UIKit ?

Réponse : SwiftUI est le framework déclaratif d’Apple pour créer des interfaces. Vous décrivez l’interface comme une fonction de l’état, et SwiftUI met à jour la vue rendue quand cet état change. UIKit est impératif : vous créez et modifiez directement des objets comme UIView, UILabel et UIViewController.

En entretien junior, ne dites pas que SwiftUI remplace UIKit partout. Beaucoup d’apps de production mélangent les deux. Une bonne réponse : SwiftUI est concis pour les nouveaux écrans et les UI pilotées par l’état ; UIKit reste important pour les anciennes bases de code, les contrôles personnalisés et les APIs pas encore entièrement couvertes par SwiftUI.

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

Rareté : Très courant Difficulté : Facile


13. Expliquez @State, @Binding et @ObservedObject dans SwiftUI.

Réponse : Ces wrappers indiquent où vit l’état et qui peut le modifier.

  • @State : État privé possédé par une vue, souvent pour un état local simple.
  • @Binding : Connexion bidirectionnelle vers un état possédé par une vue parente. L’enfant peut le lire et l’écrire sans en être propriétaire.
  • @ObservedObject : Référence vers des données observables externes. À utiliser quand un autre objet possède les données et que la vue doit se rafraîchir lors des changements. Dans du SwiftUI récent, vous pouvez aussi voir le framework Observation et @Observable, mais beaucoup d’entretiens posent encore ces questions.
class UserData: ObservableObject {
    @Published var name = ""
}

struct ParentView: View {
    @State private var isOn = false
    @ObservedObject var userData = UserData()

    var body: some View {
        VStack {
            ChildView(isOn: $isOn)
            Text(userData.name)
        }
    }
}

struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

Rareté : Courant Difficulté : Moyenne


14. Comment créez-vous une liste dans SwiftUI ?

Réponse : Utilisez la vue List pour afficher des collections de données défilables.

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange", "Grape"]
    
    var body: some View {
        List(fruits, id: \.self) { fruit in
            Text(fruit)
        }
    }
}

// Avec un modèle personnalisé
struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TaskListView: View {
    let tasks = [
        Task(title: "Buy groceries"),
        Task(title: "Walk the dog")
    ]
    
    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

Rareté : Courant Difficulté : Facile


15. Que sont les Modificateurs de Vue dans SwiftUI ?

Réponse : Les modificateurs de vue sont des méthodes qui créent une nouvelle vue avec des propriétés modifiées. Ils sont chaînables.

  • Modificateurs courants : .padding(), .background(), .foregroundColor(), .font(), .frame()
  • L'ordre compte : Les modificateurs sont appliqués en séquence
Text("Hello, World!")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.yellow)
    .cornerRadius(10)
    .shadow(radius: 5)

// Modificateur personnalisé
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

extension View {
    func primaryButton() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

Rareté : Courant Difficulté : Facile


Données et Réseau (5 Questions)

16. Comment effectuer une requête réseau dans iOS ?

Réponse : Utilisez URLSession. En Swift moderne, une réponse adaptée à un niveau junior peut montrer async/await, puis préciser que les anciennes bases de code utilisent encore des completion handlers. Une bonne réponse de production valide aussi la réponse HTTP, gère les erreurs, décode des modèles Codable et met à jour l’UI sur le main actor.

struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode(User.self, from: data)
}

Rareté : Très courant Difficulté : Moyenne


17. Qu'est-ce que Codable en Swift ?

Réponse : Codable est un alias de type pour les protocoles Encodable & Decodable. Il permet une conversion facile entre les types Swift et les représentations externes comme JSON.

  • Synthèse automatique : Swift génère automatiquement le code d'encodage/décodage si toutes les propriétés sont Codable.
  • Clés personnalisées : Utilisez l'énumération CodingKeys pour mapper différents noms de propriétés.
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

// Décodage JSON
let json = """
{
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json)

// Encodage vers JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(user)

Rareté : Très courant Difficulté : Facile


18. Comment persistez-vous les données localement dans iOS ?

Réponse : Plusieurs options pour la persistance des données locales :

  • UserDefaults : Stockage clé-valeur simple pour de petites quantités de données (paramètres, préférences)
  • Système de fichiers : Enregistrer les fichiers dans le répertoire Documents ou Cache
  • Core Data : Le framework d'Apple pour la gestion et la persistance des graphes d'objets
  • SQLite : Base de données relationnelle
  • Keychain : Stockage sécurisé pour les données sensibles (mots de passe, jetons)
// UserDefaults
UserDefaults.standard.set("John", forKey: "username")
let username = UserDefaults.standard.string(forKey: "username")

// Système de fichiers
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = documentsPath.appendingPathComponent("data.json")

// Écrire
try data.write(to: filePath)

// Lire
let loadedData = try Data(contentsOf: filePath)

Rareté : Très courant Difficulté : Facile


19. Quelle est la différence entre opérations synchrones et asynchrones ?

Réponse : Une opération synchrone bloque le thread courant jusqu’à sa fin. Sur le thread principal, c’est risqué, car l’app peut ne plus répondre. Une opération asynchrone lance le travail et laisse le thread continuer ; le résultat arrive plus tard via async/await, un completion handler, un delegate ou un publisher.

En entretien iOS, reliez ce point à la réactivité de l’UI : les appels réseau, les fichiers et les traitements lourds ne doivent pas bloquer le main thread, et les changements d’UI doivent passer par le main actor.

func loadProfile() async {
    do {
        let user = try await fetchUser(id: 42)
        await MainActor.run {
            self.nameLabel.text = user.name
        }
    } catch {
        await MainActor.run {
            self.errorLabel.text = "Could not load profile"
        }
    }
}

Rareté : Courant Difficulté : Facile


20. Qu’est-ce que Grand Central Dispatch (GCD) ?

Réponse : GCD est l’API bas niveau d’Apple pour planifier du travail sur des dispatch queues. La main queue sert au travail d’UI, et les global queues peuvent exécuter du travail en arrière-plan avec différentes priorités de qualité de service.

Dans du Swift récent, vous utiliserez souvent Swift concurrency (async/await, Task et les actors) en premier, car le code asynchrone devient plus lisible. GCD reste important, car beaucoup de projets UIKit, bases de code anciennes et APIs à callbacks utilisent DispatchQueue.main.async ou des queues en arrière-plan.

DispatchQueue.global(qos: .userInitiated).async {
    let image = renderThumbnail()

    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

Rareté : Courant Difficulté : Moyenne


Newsletter subscription

Conseils de carrière hebdomadaires qui fonctionnent vraiment

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

Démarquez-vous auprès des Recruteurs et Décrochez Votre Emploi de Rêve

Rejoignez des milliers de personnes qui ont transformé leur carrière avec des CV alimentés par l'IA qui passent les ATS et impressionnent les responsables du recrutement.

Commencer maintenant

Partager cet article

Faites Compter Vos 6 Secondes

Les recruteurs scannent les CV pendant seulement 6 à 7 secondes en moyenne. Nos modèles éprouvés sont conçus pour capter l'attention instantanément et les inciter à continuer la lecture.