Fiche 16.08 — Qualité pro outillée : SwiftLint, CI GitHub Actions, DocC et Git avancé
Objectif
Maîtriser les marqueurs de séniorité qui crédibilisent un freelance iOS : Git avancé, linting automatisé, CI qui build/test/lint à chaque PR, documentation pro et code review rigoureuse. C'est ce qui sépare « je code » de « je livre en équipe ».
1. Git avancé : ce qu'on attend d'un senior
rebase -i (squash / reword)
Avant d'ouvrir une PR, tu nettoies ton historique. Une PR avec 12 commits « wip », « fix typo », « ça marche enfin » fait fuir un reviewer.
Dans l'éditeur :
squash(ous) : fusionne le commit dans le précédent.fixup(f) : idem mais jette le message du commit fusionné.reword(r) : garde le commit, change juste le message.drop(d) : supprime le commit.
Règle d'or : ne rebase jamais une branche déjà partagée/mergée (tu réécris l'historique des autres). Sur ta branche feature perso, libre à toi.
stash
Tu dois changer de branche en urgence mais ton travail n'est pas commitable :
.gitignore iOS
Le piège classique du junior : committer DerivedData ou un fichier de secrets. Ton .gitignore minimal :
xcuserdata/ contient tes préférences perso (breakpoints, layout) → bruit pur dans les PR. Les .xcconfig portent souvent des clés d'API → on les sort du repo et on les injecte via la CI ou un fichier d'exemple Secrets.example.xcconfig versionné, lui.
Conventional Commits
Format standardisé qui rend l'historique lisible et permet de générer un changelog automatiquement :
Avec ! ou un footer BREAKING CHANGE: pour signaler une rupture d'API. C'est la base de la versioning sémantique automatisée (feat → minor, fix → patch, breaking → major).
Git Flow vs trunk-based
| Git Flow | Trunk-based | |
|---|---|---|
| Branches longues | develop, release, hotfix | quasi aucune |
| Merge | par release | continu sur main |
| Pour qui | gros cycles, versions planifiées | équipes rapides, CI/CD, feature flags |
En freelance/startup, trunk-based domine aujourd'hui : branches courtes (feature/xxx), PR mergée vite, main toujours déployable. Git Flow reste vu chez de grosses boîtes au cycle de release lent. Sache expliquer les deux, ce sont des questions d'entretien fréquentes.
Résoudre un conflit non trivial
Pas idéal : accepter aveuglément « current » ou « incoming » sans lire.
Préférable : comprendre les deux intentions.
Ici les deux changements sont valides → tu fusionnes la logique (state = .error(mapError(error)) est probablement la bonne, mais vérifie). Puis :
Active git config --global merge.conflictstyle zdiff3 : il affiche aussi l'ancêtre commun, ce qui clarifie qui a changé quoi. En cas de doute total : git rebase --abort et tu recommences à tête reposée.
2. SwiftLint + SwiftFormat
Deux outils complémentaires : SwiftLint détecte (et parfois corrige) les problèmes de style et certaines erreurs ; SwiftFormat reformate le code automatiquement.
Installer
.swiftlint.yml de base
Règles utiles à connaître : force_unwrapping, force_cast, force_try (les !, as!, try! qui crashent), cyclomatic_complexity, weak_delegate. swiftlint --fix corrige automatiquement ce qui est corrigeable.
Intégrer : build phase
Dans Xcode → Target → Build Phases → New Run Script Phase :
Les violations apparaissent comme warnings/errors directement dans Xcode. Pas idéal seul : ça ralentit le build et un dev peut ignorer un warning. Préférable : combiner build phase + pre-commit + CI.
Intégrer : pre-commit hook
.git/hooks/pre-commit (ou via l'outil pre-commit) :
Le commit est refusé si le code n'est pas propre. La CI reste le dernier rempart : même si quelqu'un contourne le hook local, la PR sera rouge.
Réflexe UIKit : tu vérifiais à l'œil dans la PR. Équivalent moderne : tu automatises, l'humain review le fond pas la forme.
3. CI GitHub Actions réelle
Objectif : à chaque PR, un runner macOS build + teste + linte ton app. Si c'est rouge, on ne merge pas.
.github/workflows/ci.yml :
Points concrets :
runs-on: macos-15est obligatoire pourxcodebuild(pas de Xcode sur Linux).CODE_SIGNING_ALLOWED=NOévite les galères de signature en CI (on ne distribue pas, on teste).xcbeautify(préinstallé sur les runners macOS GitHub ; oubrew install xcbeautify) rend les logs lisibles ;set -o pipefailest obligatoire pour ne pas masquer un échec derrière le pipe.--strictsur SwiftLint = la PR échoue au moindre warning.
Pour aller plus loin : cache des dépendances SPM (actions/cache), upload du resultBundle en artefact, matrix de plusieurs simulateurs.
4. Documentation
/// doc comments + DocC
Les commentaires /// ne sont pas du blabla : Xcode les affiche en Quick Help (⌥-clic) et DocC les compile en doc navigable.
Pour générer la doc : Xcode → Product → Build Documentation (⌃⇧⌘D). Ça ouvre le DocC viewer avec tes types, symboles et articles .md. Indispensable si tu livres un SDK ou un package à d'autres devs.
README pro
Un README qui inspire confiance contient :
Le badge CI vert en haut du README = signal immédiat que le projet est sérieux.
ADR (Architecture Decision Record)
Un ADR documente pourquoi une décision technique a été prise. Un fichier court par décision dans /Docs/adr/ :
En entretien, savoir ce qu'est un ADR montre que tu penses « équipe sur la durée », pas juste « ça compile ».
5. Code review iOS : la checklist
Une review iOS solide chasse des choses précises. Ta checklist :
- Force unwrap / force try : un
!outry!non justifié = crash potentiel en prod. Refuser. [weak self]dans les closures qui survivent (Task longue, completion réseau, Combinesink) → retain cycle sinon.@MainActor: tout ce qui touche l'UI doit tourner sur le main thread. Une mutation de@State/@Observablehors MainActor est un bug latent.- Réseau dans la View : un
URLSessionou une logique métier directement dansbody= anti-pattern. Ça va dans le ViewModel/service. - Secrets committés : clé API en dur,
GoogleService-Info.plistexposé, token dans le code → bloquant absolu. - Tests : un fix sans test de non-régression, une feature sans couverture du ViewModel.
- Gestion d'erreur :
try?qui avale silencieusement, erreur technique brute affichée à l'user.
Donner une review
Sois précis et bienveillant, commente le code pas la personne. Pas idéal : « c'est faux ». Préférable : « ce self.user! crashera si l'appel échoue avant l'assignation — un guard let ici ? ». Distingue le bloquant (must) du goût perso (nit:).
Recevoir une review
Ne le prends pas perso. Réponds à chaque commentaire (corrigé / pas d'accord car X). Si tu n'es pas d'accord, argumente techniquement, ne mergue pas en force.
6. Process de bug pro
Le réflexe junior : « je vois le bug, je patche vite ». Le réflexe senior, en 3 temps :
Exemple : crash quand la liste est vide.
Tu fixes totalPrice, le test passe. Bonus : tu commits avec fix: corrige le crash du panier vide (+ test) et la review est triviale à valider.
Points à connaître
git rebase -isur une branche partagée : tu réécris l'historique commun → tout le monde casse. Squash seulement sur ta branche feature avant la PR.- Secrets committés une fois = compromis pour toujours :
git rm --cachedne suffit pas, ils restent dans l'historique. Il faut purger (git filter-repo) ET révoquer/régénérer la clé. Mieux vaut ne jamais les committer. - CI sans
--strictsur SwiftLint : les warnings s'accumulent et plus personne ne les lit. Strict ou rien. - Doc comments
///≠ commentaires//: seuls les///(et/** */) alimentent Quick Help et DocC. Un//reste invisible dans la doc générée.
Exercice (15-20 min)
Sur un projet Xcode existant (ou un nouveau) :
- Crée
.swiftlint.ymlà la racine avec :line_lengthà 120 (warning),force_castetforce_tryenerror, etforce_unwrappingen opt-in. Lanceswiftlintet corrige au moins une violation. - Crée
.github/workflows/ci.ymlqui, sur chaquepull_requestversmain: sélectionne Xcode, lanceswiftlint --strict, puisxcodebuild testsur un simulateur iPhone 16. - Vérifie que ton
.gitignoreexclut bienDerivedData/,xcuserdata/et les*.xcconfig. - Fais un commit en Conventional Commits (
chore: ajoute SwiftLint et la CI).
Bonus : pousse sur une branche, ouvre une PR, et observe le job CI tourner (vert/rouge).
Question d'entretien
Q : « Comment tu garantis la qualité du code en équipe ? C'est quoi ta CI ? »
R modèle : « Plusieurs garde-fous. En local, SwiftLint + SwiftFormat en build phase et en pre-commit, donc le style est non négociable avant même le commit. Côté process, tout passe par une PR avec au moins une review : je check les force unwrap, les [weak self], que rien de métier ou de réseau ne traîne dans une View, et qu'aucun secret n'est committé. Et une CI GitHub Actions sur runner macOS qui, à chaque PR, fait swiftlint --strict puis xcodebuild test sur un simulateur — si c'est rouge, on ne merge pas. Pour les bugs, je reproduis, j'écris un test de non-régression qui échoue, puis je fixe. Comme ça la qualité ne dépend pas de la discipline de chacun, elle est outillée. »
Q : « Git Flow ou trunk-based ? »
R modèle : « Ça dépend du rythme de release. Git Flow avec develop/release/hotfix convient aux cycles longs et versionnés. Mais pour la plupart des équipes produit aujourd'hui je préfère le trunk-based : branches courtes, PR mergée vite, main toujours déployable, soutenu par une bonne CI et des feature flags pour cacher le travail en cours. Moins de merges douloureux, livraison plus continue. »
Résumé
- Git avancé :
rebase -ipour nettoyer (squash/reword) avant la PR,stashpour mettre de côté,.gitignoreiOS qui exclut DerivedData/xcuserdata/secrets. - Conventional Commits (
feat:/fix:/...) : historique lisible + versioning auto. - Trunk-based domine en équipe produit ; sache expliquer Git Flow aussi.
- SwiftLint + SwiftFormat :
.swiftlint.yml, intégration build phase + pre-commit,--stricten CI. - CI GitHub Actions sur
macos-15: build + test (xcodebuild test) + lint à chaque PR ; rouge = pas de merge. - Documentation :
///+ DocC (Build Documentation), README pro avec badges, ADR pour tracer les décisions. - Code review iOS : force unwrap,
[weak self],@MainActor, réseau dans la View, secrets — la checklist. - Bug : reproduire → test de non-régression rouge → fixer → vert.
- Tout ça = la qualité est outillée, pas laissée à la discipline. C'est le marqueur de séniorité que les recruteurs cherchent.