Conditional Compilation dans les applications mobiles — essence, directives et principe de fonctionnement

Auteur : IT Sectr Publié le : 2026-06-01 Temps de lecture : 9 min

La Conditional Compilation permet au compilateur d'inclure ou d'ignorer des parties du code source en fonction de conditions connues au moment de la compilation. Selon The Swift Programming Language (2026), la directive #if est traitée lors de l'analyse AST avant la génération de code machine. Conditional Compilation offre aux développeurs la capacité de maintenir une base de code unique pour plusieurs plates-formes et configurations sans duplication.

Points clés

  • Conditional Compilation — technique de compilation sélective du code selon les conditions de plateforme, de configuration ou de version du langage.
  • Directives #if, #elseif, #else, #endif — les principales constructions de compilation conditionnelle en Swift, C, C++, Objective-C.
  • Kotlin n'a pas de directives de préprocesseur — à la place, BuildConfig, expect/actual et sourceSets sont utilisés.
  • Avantage — le code pour les plates-formes inappropriées n'est pas compilé, réduisant la taille du binaire et éliminant les erreurs.
  • iOS/macOS code partagé — Conditional Compilation est le fondement du développement de frameworks multiplateformes d'Apple.

Qu'est-ce que la Conditional Compilation

Conditional Compilation est un mécanisme par lequel le compilateur analyse les directives de compilation conditionnelle et n'inclut dans le binaire de sortie que les blocs de code dont les conditions sont remplies. Cela permet d'avoir une base de code unique qui s'adapte à différentes plates-formes et configurations cibles.

Le concept vient du C/C++ avec les directives de préprocesseur #ifdef, #ifndef, #endif. Dans les langages modernes (Swift, Rust, Go), le mécanisme fonctionne au niveau du compilateur sans préprocesseur séparé, ce qui augmente la sécurité : les blocs conditionnels doivent être syntaxiquement corrects même s'ils ne sont pas compilés.

Selon la session Apple WWDC « Embrace Swift » (2025), environ 40% des projets Swift utilisent la compilation conditionnelle pour prendre en charge iOS et macOS dans une seule cible. Pour les projets avec UIKit et SwiftUI, le code de l'interface utilisateur est souvent divisé par des directives #if os(iOS) et #if os(macOS), permettant la réutilisation de la logique métier.

Le principal avantage est la sécurité au moment de la compilation. Le code pour une plateforme inappropriée n'est pas seulement non exécuté, mais non compilé. Cela signifie que les erreurs dans le code spécifique à iOS n'apparaîtront pas lors de la compilation pour macOS, et vice versa. Les vérifications à l'exécution n'offrent pas de telles garanties.

Conditional Compilation dans Swift

Swift fournit quatre directives clés : #if, #elseif, #else, #endif. Contrairement au préprocesseur C, Swift exige la correction syntaxique du code dans toutes les branches — le compilateur analyse tout le code mais génère du code machine uniquement pour les branches actives.

Vérifications de plateforme os()

Swift prend en charge des fonctions de vérification intégrées : os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Ces fonctions vérifient la plateforme cible pour laquelle l'application est compilée. La combinaison avec && et || permet de créer des conditions complexes.

swift
// Code unifié pour iOS, macOS et tvOS
import Foundation

class PlatformService {
    func getSystemVersion() -> String {
        #if os(iOS) || os(tvOS)
            return UIDevice.current.systemVersion
        #elseif os(macOS)
            let vers = ProcessInfo.processInfo.operatingSystemVersion
            return "\(vers.majorVersion).\(vers.minorVersion)"
        #else
            return "unknown"
        #endif
    }
}

Vérifications de version du compilateur

Swift prend en charge la vérification de version du compilateur : #if swift(>=5.9). Ceci est utile pour les bibliothèques et frameworks qui supportent plusieurs versions de Swift. Les nouvelles fonctionnalités du langage (comme les macros dans Swift 5.9) peuvent être protégées par une telle vérification.

swift
// Compatibilité ascendante
#if swift(>=5.9)
    @MainActor
    struct ModernView: View {
        var body: some View {
            Text("Modern SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Legacy SwiftUI")
        }
    }
#endif

Vérification de disponibilité du module canImport()

La fonction canImport(ModuleName) vérifie si le module spécifié est disponible dans l'environnement de compilation actuel. C'est le mécanisme le plus flexible : il n'est lié à aucune plateforme spécifique. Par exemple, le code utilisant CoreHaptics ne sera compilé que sur les appareils où ce framework est disponible.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Implémentation du retour haptique
        }
    }
#endif

Alternatives dans Kotlin et Android

Kotlin en tant que langage n'a pas de directives de préprocesseur. À la place, l'écosystème Android offre trois alternatives : les champs BuildConfig (vérifications à l'exécution), sourceSets (remplacement de fichiers entiers) et expect/actual (dans Kotlin Multiplatform).

Source Sets dans Gradle

Les sourceSets de Gradle permettent d'avoir différentes implémentations de classes pour différents flavors ou types de compilation. Dans le répertoire src/debug/ se trouve l'implémentation de débogage, dans src/release/ — l'implémentation de release. Lors de la compilation, Gradle sélectionne le sourceSet approprié et ne compile que ses fichiers.

kotlin
// src/debug/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        Log.d(tag, message)
    }
}

// src/release/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        // No-op en release
    }
}

Expect/Actual dans Kotlin Multiplatform

KMP fournit le mécanisme expect (déclaration dans le code commun) et actual (implémentation pour une plateforme spécifique). C'est un mécanisme au moment de la compilation : pour iOS, l'implémentation actual du sourceSet iOS est compilée ; pour Android — du sourceSet Android. Les implémentations non ciblées ne sont pas compilées.

kotlin
// commonMain — déclaration expect
expect fun getPlatformName(): String

// androidMain — actual pour Android
actual fun getPlatformName(): String =
    "Android \${Build.VERSION.SDK_INT}"

// iosMain — actual pour iOS
actual fun getPlatformName(): String =
    UIDevice.current.systemName() + " " + UIDevice.current.systemVersion

Préprocesseur C/C++ et NDK

Lors du développement de bibliothèques natives via Android NDK, le préprocesseur classique C/C++ avec les directives #ifdef, #ifndef, #define est utilisé. Contrairement à Swift, le préprocesseur C fonctionne au niveau du texte — le code dans les branches inactives peut être syntaxiquement incorrect.

Drapeaux de plateforme NDK

NDK définit des macros pour chaque plateforme : __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Pour les architectures : __arm__, __aarch64__, __x86_64__. Ces macros sont définies automatiquement par le compilateur lors de la compilation pour la plateforme cible.

cpp
// Code natif pour Android et iOS
#include <cstdint>

#ifdef __ANDROID__
    int32_t getJniEnv(JNIEnv* env) {
        return env->GetVersion();
    }
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    int32_t getOsVersion() {
        #if TARGET_OS_IOS
            return "iOS";
        #elif TARGET_OS_OSX
            return "macOS";
        #endif
    }
#endif

Lorsqu'on travaille avec NDK, il est important de se rappeler que le préprocesseur C/C++ est un remplacement de texte. S'il y a une erreur syntaxique dans une branche inactive, le compilateur ne la verra pas, mais si un #define incorrect casse une branche active — l'erreur apparaîtra. Il est recommandé de minimiser les chaînes de #define et d'utiliser des constantes constexpr.

Pour Rust, qui est également utilisé dans le développement mobile via UniFFI et Mozilla Application Services, il existe son propre mécanisme — les feature flags dans Cargo.toml. Les drapeaux comme #[cfg(target_os = « android »)] dans Rust fonctionnent de manière similaire aux directives Swift : la vérification est effectuée au niveau du compilateur, pas du préprocesseur. Cela fait de Rust un choix attrayant pour les bibliothèques natives qui doivent être compilées pour Android et iOS à partir d'une seule base de code.

Scénarios pratiques et antipatterns

Conditional Compilation est efficace dans des scénarios strictement définis. Lorsqu'elle est utilisée incorrectement, elle crée un code smell difficile à tester et à maintenir. Examinons les scénarios corrects et les erreurs typiques.

Scénarios corrects

Le premier scénario est l'abstraction de plateforme : une seule Façade avec Conditional Compilation sélectionnant l'implémentation de plateforme en interne. Le second est le débogage et le profilage : des outils de développement qui ne doivent pas se retrouver dans la release. Le troisième est la compatibilité ascendante : la prise en charge des anciennes versions d'OS jusqu'à ce que la version minimale soit mise à jour.

ScénarioLangageCondition
Abstraction de plateformeSwift#if os(iOS)
DébogageSwift/ObjC#if DEBUG
Compatibilité ascendanteSwift#if swift(>=5.7)
Bibliothèque nativeC/C++#ifdef __ANDROID__
Test A/BJava/KotlinBuildConfig.FLAVOR

Antipatterns

L'antipattern le plus dangereux est la prolifération des directives dans tout le code. Si un fichier sur deux contient #if, c'est un signe que l'architecture a besoin d'être refactorisée. La bonne solution est d'extraire le code de plateforme derrière des protocoles/interfaces et d'utiliser l'Injection de Dépendances.

  • #if dans chaque fichier — un antipattern architectural. Le code de plateforme doit être isolé derrière des protocoles.
  • #if imbriqués — deviennent rapidement illisibles. La profondeur d'imbrication ne doit pas dépasser 2 niveaux.
  • Duplication de fonctions entières — si une fonction est complètement copiée dans #if et #else, elle doit être extraite dans une partie commune.
  • Tests — le code dans les branches inactives n'est pas testé. Des compilations CI de toutes les combinaisons possibles sont nécessaires.
  • Drapeaux magiques — des drapeaux non documentés que la nouvelle équipe de développement ne connaît pas.

Foire aux questions

En quoi Conditional Compilation diffère-t-elle des vérifications à l'exécution ?

Conditional Compilation fonctionne au moment de la compilation : le code inactif ne se retrouve pas dans le binaire. Les vérifications à l'exécution (if / switch) sont toujours compilées, la condition est vérifiée pendant l'exécution. La première est plus sûre et efficace, la seconde est plus flexible (peut être modifiée sans reconstruire).

Peut-on utiliser #if à l'intérieur d'une fonction dans Swift ?

Oui, Swift permet les directives #if à l'intérieur des fonctions, des boucles et même à l'intérieur des expressions. C'est l'une des fonctionnalités qui manquait dans les premières versions de Swift. Par exemple : let x = #if DEBUG 1 #else 0 #endif — code valide.

Pourquoi Kotlin n'a-t-il pas ajouté de préprocesseur ?

Les développeurs de Kotlin ont délibérément refusé le préprocesseur, le considérant comme une source de code fragile. À la place, ils proposent expect/actual (sécurité au moment de la compilation) et les sourceSets de Gradle (isolation au niveau des fichiers). Les deux approches sont plus fiables que le remplacement de texte.

Comment tester le code dans les branches #if inactives ?

Compilez l'application avec différentes combinaisons de drapeaux dans le CI. Pour Swift : configurez des schémas Xcode séparés avec différentes Active Compilation Conditions. Pour Android : configurez des Build Variants séparés et exécutez des tests pour chacun. L'automatisation est obligatoire.

Que se passe-t-il si la condition #if contient une erreur syntaxique ?

Dans Swift, la condition #if est une directive du compilateur. Si la condition elle-même est syntaxiquement incorrecte (par exemple, une faute de frappe dans le nom os()), le compilateur émettra une erreur de compilation. En C/C++, le préprocesseur ne trouvera tout simplement pas la macro et la condition deviendra fausse.

Résumé

  • Conditional Compilation — technique de compilation qui exclut le code non ciblé au moment de la compilation, contrairement aux vérifications à l'exécution.
  • Swift prend en charge #if avec os(), canImport(), swift() — des directives sûres à la compilation exigeant la correction syntaxique de toutes les branches.
  • Kotlin utilise expect/actual et les sourceSets de Gradle au lieu d'un préprocesseur — des approches plus fiables mais moins flexibles.
  • C/C++ dans NDK utilise le préprocesseur de texte classique #ifdef / #ifndef avec les macros de plateforme __ANDROID__, __APPLE__.
  • Utilisation correcte — abstraction de plateforme, débogage, compatibilité ascendante. Utilisation incorrecte — #if dans chaque fichier, imbrication profonde, drapeaux magiques.
  • CI est obligatoire — toutes les combinaisons de drapeaux doivent être compilées et testées automatiquement, sinon le code dans les branches inactives devient mort.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi