Vous avez peut-être remarqué ce réglage "Optimization Level" dans les Build Settings de votre projet Xcode. -Onone, -O, -Osize... C'est quoi tout ça ? Et surtout, pourquoi votre app rame en Debug à un tel point que vous avez envie de jeter votre telephone contre le mur mais vole en Release tel un albatros alpha ? Décortiquons ensemble ces options qui font une vraie différence.
1. C'est quoi l'optimisation du compilateur ?
Le principe
Quand vous écrivez du code Swift, le compilateur (swiftc) le transforme en code machine exécutable (donc en binaire).
Entre votre code source et le binaire final, plusieurs passes intermédiaires ont lieu (SIL, ARC, LLVM), et le compilateur peut faire différents compromis :
- Guarder la structure du code : facile à débug, mais peu performant
- Réorganiser et spécialiser : plus rapide à l'exécution, mais plus difficile à debug
- Réduire la taille du code généré : binaire plus petit, parfois légèrement moins rapide
Ces choix sont contrôlés par le ✨ niveau d'optimisation ✨.
Pourquoi ça compte ?
La différence est loin d'être anecdotique :
| Aspect | Sans optimisation | Avec optimisation |
|---|---|---|
| Vitesse d'exécution | Baseline | 2× à 10× plus rapide |
| Taille du binaire | Plus grande | Jusqu'à ~30% plus petite |
| Temps de compilation | Rapide | Plus lent |
| Debugging | Excellent | Limité |
Les étapes principales du compilateur Swift rapidement
-
AST (Abstract Syntax Tree)
Représentation proche de votre code Swift. -
SIL (Swift Intermediate Language)
Langage intermédiaire spécifique à Swift, où ont lieu :- ARC optimizations
- inlining Swift-aware
- spécialisation des generics
- élimination des abstractions Swift
-
LLVM IR
Le SIL est abaissé en LLVM IR, où s'appliquent :- loop unrolling
- vectorisation SIMD
- optimisations bas niveau CPU
- register allocation
-
Code machine
Génération du binaire final.
Vous ne comprenez pas tout ? Moi aussi, mais on apprendra ça ensemble dans un autre article histoire de mieux comprendre ce qu'il se passe sous le capot. L'idée était de vous montrer qu'il y a plusieurs processus lancés lors de la compilation et que vos réglages d'optimisations ont un impact sur ces éléments, on va voir ça tout de suite.
2. Les différents niveaux d'optimisation
Swift propose quatre niveaux d'optimisation principaux.
-Onone (No optimization)
Sur Xcode vous voyez :
Optimization Level = None [-Onone]
Ce que ça fait :
- Aucune optimisation
- Le code généré correspond à ce que vous avez écrit
- Toutes les variables sont préservées
- Tous les appels de fonctions sont explicites
Quand l'utiliser :
- Pour débuger
Avantages :
- Compilation rapide
- Debugging parfait (breakpoints, variables)
Inconvénients :
- Performances médiocres
- Binaire plus volumineux
-O (Optimize for Speed)
Sur Xcode vous voyez :
Optimization Level = Optimize for Speed [-O]
Ce que ça fait :
- Optimisations agressives pour la vitesse
- Inlining des fonctions (le code est copié au lieu d'être appelé)
- Élimination du code mort
- Déroulage des boucles (loop unrolling)
- Propagation des constantes
- Et bien d'autres transformations...
Quand l'utiliser :
- Release : c'est le choix par défaut et recommandé
- Quand la performance est prioritaire
- Pour la production
Avantages :
- Performances optimales
- Le meilleur rapport qualité/vitesse
Inconvénients :
- Compilation plus lente
- Debugging très limité (variables "optimized out")
- Stack traces parfois incomplètes
-Osize (Optimize for Size)
Sur Xcode vous voyez :
Optimization Level = Optimize for Size [-Osize]
Ce que ça fait :
- Optimise pour réduire la taille du binaire
- Moins d'inlining (évite la duplication de code)
- Préfère les appels de fonction aux copies
- Garde certaines optimisations de vitesse quand elles ne gonflent pas le code
Quand l'utiliser :
- Apps avec contraintes de taille (App Clips limités à 15 MB)
- Extensions (widgets, notifications) avec budgets serrés
- Marchés émergents où la taille de téléchargement compte
Avantages :
- Binaire significativement plus petit (10-30% selon les cas)
- Performances correctes (pas aussi rapide que -O, mais pas catastrophique)
Inconvénients :
- Légèrement plus lent que -O
- Debugging limité comme -O
-Ounchecked (Unchecked optimization)
Sur Xcode vous voyez :
Optimization Level = Optimize for Speed, Unchecked [-Ounchecked]
L'option dangereuse
Ce que ça fait :
- Toutes les optimisations de -O
Supprime les vérifications de sécurité à l'exécution :
- Débordements arithmétiques (overflow)
- Accès aux tableaux hors limites
- Force-unwrap de nil
- Préconditions et assertions
Quand l'utiliser :
- Quasi jamais en pratique
- Code ultra-critique en performance déjà prouvé correct
- Si Tanos reviens, quoi que j'ai un doute ...
Avantages :
- Performances maximales théoriques
- Peut faire la différence dans du code numérique intensif
Inconvénients :
- Comportement indéfini si une vérification aurait échoué
- Crashs silencieux, corruption mémoire possible
- Très difficile à débugger
- Failles de sécurité potentielles
Le conseil : oubliez que ça existe. Les gains sont marginaux et les risques présents. Si vous avez besoin de ce niveau de performance, vous savez déjà ce que vous faites et c'est surement pas un wrapper ChatGPT. (Si vous l'utilisez pour de très bonne raison hésitez pas à m'envoyer un petit mail je suis super curieuse de ce cas d'usage)
3. Whole Module Optimization (WMO)
Au-delà du niveau d'optimisation, il y a un autre réglage crucial : Whole Module Optimization.
C'est quoi ?
Par défaut, Swift compile chaque fichier séparément. Avec WMO, le compilateur voit tout le module d'un coup et peut faire des optimisations impossibles autrement.
Sur Xcode vous voyez :
Compilation Mode = Whole Module
Ce que ça permet
Sans WMO (fichier par fichier) :
// FileA.swift
func helperFunction() -> Int { return 42 }
// FileB.swift
func mainFunction() -> Int {
return helperFunction()
// Le compilateur ne sait pas ce que fait helperFunction
// il doit faire un vrai appel de fonction
}Avec WMO :
// Le compilateur voit les deux fichiers ensemble
// il peut inliner helperFunction directement
func mainFunction() -> Int {
return 42 // Plus d'appel de fonction !
}Optimisations permises par WMO
- Cross-file inlining : inliner des fonctions définies dans d'autres fichiers
- Dead code elimination globale : supprimer le code jamais appelé dans tout le module
- Dévirtualisation : transformer des appels dynamiques en appels statiques
- Spécialisation des generics : générer des versions optimisées pour les types concrets
Impact sur les performances
WMO peut apporter 10-20% de gains supplémentaires, parfois plus. C'est particulièrement efficace pour :
- Les protocoles avec des implémentations connues
- Les generics utilisés avec des types concrets
- Le code bien structuré en petites fonctions
Le compromis : temps de compilation
| Mode | Temps de compilation incrémental | Temps de compilation clean |
|---|---|---|
| Fichier par fichier | Rapide (recompile uniquement les fichiers modifiés) | Normal |
| Whole Module | Lent (recompile tout le module) | Similaire |
En Debug, vous voulez des builds incrémentaux rapides utilisez fichier par fichier. En Release, vous voulez les meilleures performances utilisez Whole Module.
Configuration dans Xcode
Le réglage se trouve dans Build Settings :
Compilation Mode:
- Incremental (fichier par fichier)
- Whole Module
Ou via le flag :
SWIFT_COMPILATION_MODE = wholemodule
4. Configurer dans Xcode
Via l'interface
- Ouvrez votre projet dans Xcode
- Sélectionnez votre target
- Onglet Build Settings
- Cherchez "Swift Compiler - Code Generation"
Vous trouverez :
| Setting | Clé | Options |
|---|---|---|
| Optimization Level | SWIFT_OPTIMIZATION_LEVEL | -Onone, -O, -Osize, -Ounchecked |
| Compilation Mode | SWIFT_COMPILATION_MODE | incremental, wholemodule |

Les options d'Optimization Level disponibles :

Configuration recommandée
Par défaut, Xcode configure ça correctement :
| Configuration | Optimization Level | Compilation Mode |
|---|---|---|
| Debug | -Onone | Incremental |
| Release | -O | Whole Module |
Voir la configuration effective
Pour vérifier ce qui est réellement appliqué :
- Build Settings > vue "Levels"
- Vous voyez d'où vient chaque valeur (défaut, projet, target, xcconfig)
5. Configurer dans les .xcconfig
Comme pour les autres flags, centraliser dans les xcconfig est une bonne pratique.
Exemple de configuration
Config/Debug.xcconfig :
// Debug : pas d'optimisation, compilation rapide
SWIFT_OPTIMIZATION_LEVEL = -Onone
SWIFT_COMPILATION_MODE = incremental
// Assertions actives
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG
Config/Release.xcconfig :
// Release : optimisations maximales
SWIFT_OPTIMIZATION_LEVEL = -O
SWIFT_COMPILATION_MODE = wholemodule
// Pas d'assertions en prod
SWIFT_ACTIVE_COMPILATION_CONDITIONS =
Config/ReleaseSmall.xcconfig (si vous avez besoin d'une config pour la taille) :
// Release optimisée pour la taille (App Clips, extensions)
SWIFT_OPTIMIZATION_LEVEL = -Osize
SWIFT_COMPILATION_MODE = wholemodule
Pour les extensions avec contraintes de taille
Si vous avez un App Clip ou un widget avec un budget taille serré :
// WidgetExtension.xcconfig
SWIFT_OPTIMIZATION_LEVEL = -Osize
SWIFT_COMPILATION_MODE = wholemodule
// Activer aussi le stripping
STRIP_STYLE = all
DEPLOYMENT_POSTPROCESSING = YES
6. Impact concret sur votre code
Ce qui change avec les optimisations
Prenons un exemple concret pour comprendre ce que fait le compilateur.
Code source :
struct Point {
var x: Double
var y: Double
func distance(to other: Point) -> Double {
let dx = other.x - x
let dy = other.y - y
return sqrt(dx * dx + dy * dy)
}
}
func totalDistance(_ points: [Point]) -> Double {
var total = 0.0
for i in 0..<(points.count - 1) {
total += points[i].distance(to: points[i + 1])
}
return total
}Avec -Onone :
- Chaque appel à
distance(to:)est un vrai appel de fonction - Les variables
dx,dy,totalexistent en mémoire - Les accès au tableau passent par des vérifications de bornes
- Vous pouvez mettre un breakpoint n'importe où
Avec -O + WMO :
distance(to:)est inliné dans la boucle- Les calculs intermédiaires restent dans les registres CPU
- La boucle peut être vectorisée (SIMD)
- Les vérifications de bornes peuvent être sorties de la boucle ou éliminées
- Le code final ressemble plus à du C optimisé
Debugging en mode optimisé
Quand vous debuggez une build Release, vous verrez souvent :
(lldb) po total
error: <EXPR>:3:1: error: use of unresolved identifier 'total'
Ou dans le debugger :
total = <optimized out>
C'est normal : la variable n'existe plus, le compilateur l'a optimisée. C'est pourquoi on debug en Debug, pas en Release.
Assertions et préconditions
Le comportement des assertions change selon la config :
| Fonction | -Onone | -O | -Ounchecked |
|---|---|---|---|
assert() | ✅ Actif | ❌ Ignoré | ❌ Ignoré |
assertionFailure() | ✅ Crash | ❌ Ignoré | ❌ Ignoré |
precondition() | ✅ Actif | ✅ Actif | ❌ Ignoré |
preconditionFailure() | ✅ Crash | ✅ Crash | ❌ Ignoré |
fatalError() | ✅ Crash | ✅ Crash | ✅ Crash |
Conséquence :
func processIndex(_ index: Int, in array: [Int]) -> Int {
// En Release avec -O, ce check est toujours exécuté
precondition(index >= 0 && index < array.count, "Index out of bounds")
// Celui-ci est ignoré en Release
assert(index >= 0, "Negative index") // Debug only
return array[index]
}Utilisez precondition pour les checks qui doivent rester en prod, assert pour ceux utiles uniquement en dev.
7. Cas d'usage avancés
Optimiser une target spécifique différemment
Vous pouvez avoir une config différente par target. Par exemple, votre framework de calcul intensif en -O même en Debug :
- Sélectionnez la target du framework
- Build Settings > Optimization Level
- Changez la valeur pour Debug en
-O
Ou via xcconfig dédié au framework.
Builds de profiling
Pour profiler avec Instruments, vous voulez :
- Les optimisations activées (sinon vous profilez du code lent)
- Les symboles de debug disponibles (pour voir les noms de fonctions)
Xcode a une configuration Profile pour ça, ou vous pouvez créer la vôtre :
// Config/Profile.xcconfig
SWIFT_OPTIMIZATION_LEVEL = -O
SWIFT_COMPILATION_MODE = wholemodule
// Garder les symboles pour le profiling
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
GCC_GENERATE_DEBUGGING_SYMBOLS = YES
Analyser la taille du binaire
Pour comprendre ce qui prend de la place et si -Osize vaut le coup :
# Taille des sections du binaire
size -m MyApp.app/MyApp
# Symboles triés par taille
nm -S --size-sort MyApp.app/MyApp | tail -50Ou utilisez l'outil Bloaty pour une analyse détaillée :
brew install bloaty
bloaty MyApp.app/MyApp -d compileunits8. Bons réflexes et erreurs fréquentes
Activer les optimisations en Debug
// On evite - Debug avec -O
Debug: SWIFT_OPTIMIZATION_LEVEL = -O
Symptômes :
- Breakpoints qui ne s'arrêtent pas où vous voulez
- Variables "optimized out"
- Stack traces incompréhensibles
- Temps de compilation Debug allongé
Gardez -Onone en Debug.
Désactiver les optimisations en Release
// On evite - Release sans optimisation
Release: SWIFT_OPTIMIZATION_LEVEL = -Onone
Votre app sera 2 à 10 fois plus lente que nécessaire. Les utilisateurs le sentiront.
Utiliser -Ounchecked "pour la perf"
Les gains sont marginaux et les risques présents, votre app peut crasher en silence en prod et c'est risquer des utilisateurs en moins sans en savoir la raison.
Oublier WMO en Release
// On evite - Release sans WMO
Release: SWIFT_COMPILATION_MODE = incremental
Vous perdez 10-20% de performances gratuites.
Configuration type recommandée
| Config | Optimization | Compilation Mode | Use case |
|---|---|---|---|
| Debug | -Onone | Incremental | Dev quotidien |
| Release | -O | Whole Module | Production |
| Profile | -O | Whole Module | Profiling Instruments |
| ReleaseSmall | -Osize | Whole Module | App Clips, extensions |
Résumé visuel
OPTIMISATION
────────────────────────────────────────►
-Onone -Osize -O -Ounchecked
│ │ │ │
▼ ▼ ▼ ▼
Aucune Taille Vitesse Vitesse max
optimisation minimale maximale (dangereux)
✅ Debug ✅ App Clips ✅ Release ⛔ Éviter
✅ Debugging ✅ Extensions ✅ Production
COMPILATION MODE
────────────────────────────────────────►
Incremental Whole Module
│ │
▼ ▼
Fichier par fichier Module entier
✅ Debug (builds rapides) ✅ Release (meilleures optims)
Pour aller plus loin
Si vous voulez creuser le sujet :
- Swift.org - Whole Module Optimization : l'article officiel
- WWDC 2015 - Optimizing Swift Performance : un peu daté mais les principes restent vrais
- Swift Compiler Performance : documentation technique du compilateur
