Architecture Modulaire Swift : Le Guide Complet
Publié le · Mis à jour le · 32 min
La modularisation d'un projet Swift transforme un monolithe difficile à maintenir en un écosystème de modules autonomes, testables et réutilisables. Ce guide couvre les fondamentaux, les patterns avancés, l'intégration avec Swift 6.2 et les meilleures pratiques pour construire des applications iOS, macOS, visionOS et serveur à l'échelle industrielle.
Pourquoi modulariser ?
Un projet Swift monolithique fonctionne bien au départ. Mais à mesure qu'il grandit, les problèmes s'accumulent : temps de compilation exponentiels, conflits de merge constants, difficultés à tester, couplage invisible entre composants.
Les symptômes d'un monolithe en souffrance
| Symptôme | Impact | Seuil critique |
|---|---|---|
Temps de build > 5 min | Productivité dégradée |
|
Conflits de merge fréquents | Friction équipe |
|
Tests lents ou instables | CI/CD inefficace |
|
Difficile d'onboarder | Courbe d'apprentissage |
|
Tout casse quand on touche X | Couplage caché | Critique |
Les bénéfices concrets de la modularisation
La modularisation apporte des gains mesurables :
Compilation incrémentale : seuls les modules modifiés sont recompilés
Parallélisation du build : les modules indépendants compilent en parallèle
Isolation des tests : chaque module a ses propres tests unitaires
Ownership clair : une équipe = un ou plusieurs modules
Réutilisation : les modules Core sont partagés entre apps
Boundaries explicites : les dépendances sont déclarées, pas implicites
Fondamentaux de Swift Package Manager
Swift Package Manager (SPM) est l'outil standard pour la modularisation depuis Swift 5.3. Il offre une intégration native avec Xcode et supporte toutes les plateformes Apple.
Anatomie d'un Package.swift
Products : Library vs Executable
| Type | Usage | Quand l'utiliser |
|---|---|---|
.library (static) | Lié au binaire final | Défaut, meilleure performance |
.library (dynamic) | Chargé au runtime | Partagé entre plusieurs targets |
.executable | Binaire standalone | CLI, scripts, serveur |
.plugin | Extension Xcode/SPM | Build tools, linters |
Targets et dépendances
Un target est une unité de compilation. Les dépendances entre targets définissent le graphe de build :
Patterns de modularisation
Pattern 1 : Feature Modules
Chaque fonctionnalité métier est un module autonome contenant UI, logique et tests :
Implémentation du point d'entrée public :
Pattern 2 : Core Modules (Infrastructure)
Les modules Core fournissent les fondations partagées :
Exemple de NetworkKit avec Swift Concurrency :
Pattern 3 : Domain Modules (Clean Architecture)
Pour les projets complexes, séparer le domaine métier des détails d'implémentation :
Implémentation avec inversion de dépendances :
Structure d'un projet modulaire
Monorepo avec packages locaux
Structure recommandée pour un projet iOS/macOS modulaire :
Root Package.swift (Umbrella)
Injection de dépendances modulaire
Pattern : Dependency Container
Pattern : Protocol Witness avec @Dependencies
Pour une approche plus testable avec swift-dependencies de Point-Free :
Swift 6.2 et modularisation
Strict Concurrency par module
Swift 6.2 permet de configurer le niveau de concurrency par target :
MainActor par défaut (defaultIsolation)
Avec Swift 6.2, les nouveaux modules UI peuvent opter pour MainActor par défaut :
Boundaries Sendable entre modules
Les frontières entre modules doivent respecter Sendable :
Optimisation des builds
Parallélisation maximale
SPM compile les modules indépendants en parallèle. Pour maximiser le parallélisme :
- Minimiser les dépendances : chaque dépendance ajoute une contrainte de séquencement
- Extraire les modules "feuilles" : modules sans dépendances internes
- Éviter les cycles : un cycle force la compilation séquentielle
Build conditionnel avec Feature Flags
Métriques de build
Script pour mesurer les temps de compilation par module :
Testing modulaire
Structure des tests par module
TestSupport module partagé
Tests avec dépendances injectées
CI/CD modulaire
GitHub Actions optimisé
Migration depuis un monolithe
Stratégie "Strangler Fig"
Migrer progressivement sans big-bang :
- Identifier les boundaries : mapper les dépendances existantes
- Extraire CoreKit : commencer par les utilities sans dépendances
- Créer les protocoles : définir les interfaces avant d'extraire
- Extraire module par module : un feature à la fois
- Nettoyer : supprimer le code dupliqué
Pièges et anti-patterns
1. Sur-modularisation
Créer trop de modules ajoute de la complexité sans bénéfice :
Règle : Si un module a moins de 5 fichiers ou < 500 lignes, il devrait probablement être fusionné.
2. Dépendances circulaires
3. Leaky abstractions
4. God Module
5. Import public transitif
Checklist modularisation
Avant de créer un nouveau module :
Le module a une responsabilité claire et unique
Le module a au moins 5 fichiers / 500 lignes de code
Les dépendances sont explicites et minimales
Pas de dépendance circulaire
L'API publique est documentée
Un target de test existe
Le module est Sendable-safe (Swift 6)
Avant de merger un PR touchant aux modules :
Le graphe de dépendances reste acyclique
Les temps de build n'ont pas augmenté significativement
Tous les tests passent
La documentation est à jour
Pour aller plus loin
Une architecture modulaire bien pensée est un investissement qui paie sur le long terme. Elle permet aux équipes de travailler en parallèle, réduit les temps de build, et rend le code plus maintenable.