Conditionele compilatie in mobiele apps — essentie, directives en werkingsprincipe

Auteur: IT Sectr Gepubliceerd: 2026-06-01 Leestijd: 9 min

Conditionele compilatie stelt de compiler in staat om delen van de broncode op te nemen of over te slaan op basis van voorwaarden die bekend zijn in de buildfase. Volgens The Swift Programming Language (2026) wordt de #if directive verwerkt in de AST-analysefase vóór het genereren van machinecode. Conditionele compilatie geeft ontwikkelaars de mogelijkheid om een ​​uniforme codebase te behouden voor meerdere platforms en configuraties zonder duplicatie.

Belangrijkste

  • Conditionele compilatie — techniek van selectieve compilatie van code op basis van platform-, configuratie- of taalversievoorwaarden.
  • Directives #if, #elseif, #else, #endif — de belangrijkste constructies van conditionele compilatie in Swift, C, C++, Objective-C.
  • Kotlin heeft geen preprocessordirectives — in plaats daarvan worden BuildConfig, expect/actual en sourceSets gebruikt.
  • Voordeel — code voor niet-passende platforms wordt niet gecompileerd, waardoor de binaire grootte afneemt en fouten worden geëlimineerd.
  • iOS/macOS gedeelde code — conditionele compilatie is de basis van de ontwikkeling van cross-platform frameworks van Apple.

Wat is conditionele compilatie

Conditionele compilatie is een mechanisme waarbij de compiler de directives voor conditionele compilatie analyseert en alleen die codeblokken in het uitvoerbare binaire bestand opneemt waarvoor aan de voorwaarden wordt voldaan. Dit maakt het mogelijk om een ​​uniforme codebase te hebben die zich aanpast aan verschillende doelplatforms en configuraties.

Het concept komt uit C/C++ met de preprocessordirectives #ifdef, #ifndef, #endif. In moderne talen (Swift, Rust, Go) werkt het mechanisme op compilerniveau zonder een aparte preprocessor, wat de veiligheid verhoogt: conditionele blokken moeten syntactisch correct zijn, zelfs als ze niet worden gecompileerd.

Volgens gegevens van de Apple WWDC-sessie „Embrace Swift” (2025) gebruikt ongeveer 40% van de Swift-projecten conditionele compilatie om iOS en macOS in één target te ondersteunen. Voor projecten met UIKit en SwiftUI wordt de UI-code vaak gescheiden door de directives #if os(iOS) en #if os(macOS), waardoor bedrijfslogica kan worden hergebruikt.

Het belangrijkste voordeel — compile-time veiligheid. Code voor een niet-passend platform wordt niet alleen niet uitgevoerd, maar ook niet gecompileerd. Dit betekent dat fouten in iOS-specifieke code niet zichtbaar worden bij het bouwen voor macOS en vice versa. Runtime-controles bieden deze garantie niet.

Conditionele compilatie in Swift

Swift biedt vier belangrijke directives: #if, #elseif, #else, #endif. In tegenstelling tot de C-preprocessor vereist Swift syntactische correctheid van code in alle takken — de compiler parseert alle code, maar genereert machinecode alleen voor actieve takken.

Platformcontroles os()

Swift ondersteunt ingebouwde controles: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Deze functies controleren het doelplatform waarvoor de app wordt gebouwd. Combinatie met && en || maakt het mogelijk om complexe voorwaarden te creëren.

swift
// Universele code voor iOS, macOS en 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
    }
}

Compiler-versiecontroles

Swift ondersteunt het controleren van de compilerversie: #if swift(>=5.9). Dit is handig voor bibliotheken en frameworks die meerdere Swift-versies ondersteunen. Nieuwe taal mogelijkheden (bijvoorbeeld macro’s in Swift 5.9) kunnen door een dergelijke controle worden beschermd.

swift
// Achterwaartse compatibiliteit
#if swift(>=5.9)
    @MainActor
    struct ModernView: View {
        var body: some View {
            Text("Modern SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Verouderd SwiftUI")
        }
    }
#endif

Beschikbaarheid van modules controleren met canImport()

De functie canImport(ModuleName) controleert of de opgegeven module beschikbaar is in de huidige buildomgeving. Dit is het meest flexibele mechanisme: het is niet gebonden aan een specifiek platform. Code die bijvoorbeeld CoreHaptics gebruikt, wordt alleen gecompileerd op apparaten waar dit framework beschikbaar is.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Implementatie van tactiele feedback
        }
    }
#endif

Alternatieven in Kotlin en Android

Kotlin als taal heeft geen preprocessor directives. In plaats daarvan biedt het Android-ecosysteem drie alternatieven: BuildConfig-velden (runtime-controles), sourceSets (vervanging van hele bestanden) en expect/actual (in Kotlin Multiplatform).

Source Sets in Gradle

Gradle sourceSets maken het mogelijk om verschillende implementaties van klassen te hebben voor verschillende varianten of buildtypen. In de map src/debug/ bevindt zich de implementatie voor debug, in src/release/ — voor release. Tijdens het bouwen selecteert Gradle de juiste sourceSet en compileert alleen de bestanden ervan.

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 in release
    }
}

Expect/Actual in Kotlin Multiplatform

KMP biedt het mechanisme expect (declaratie in gedeelde code) en actual (implementatie voor een specifiek platform). Dit is een compile-time mechanisme: voor iOS wordt de actual-implementatie uit de iOS sourceSet gecompileerd, voor Android — uit de Android sourceSet. Niet-doelgerichte implementaties worden niet gecompileerd.

kotlin
// commonMain — expect-declaratie
expect fun getPlatformName(): String

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

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

C/C++ preprocessor en NDK

Bij de ontwikkeling van native bibliotheken via Android NDK wordt de klassieke C/C++ preprocessor gebruikt met de directives #ifdef, #ifndef, #define. In tegenstelling tot Swift werkt de C-preprocessor op tekstueel niveau — code in inactieve takken kan syntactisch onjuist zijn.

NDK platformvlaggen

NDK definieert macro’s voor elk platform: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Voor architecturen: __arm__, __aarch64__, __x86_64__. Deze macro’s worden automatisch ingesteld door de compiler bij het bouwen voor het doelplatform.

cpp
// Native code voor Android en 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

Bij het werken met NDK is het belangrijk om te onthouden dat de C/C++ preprocessor een tekstuele vervanging is. Als er een syntaxisfout in een inactieve tak zit, ziet de compiler deze niet, maar als door een onjuiste #define de actieve tak beschadigd raakt — zal de fout optreden. Het wordt aanbevolen om #define-ketens te minimaliseren en constexpr-constanten te gebruiken.

Voor Rust, dat ook in mobiele ontwikkeling wordt gebruikt via UniFFI en Mozilla Application Services, bestaat er een eigen mechanisme — feature flags in Cargo.toml. Vlaggen zoals #[cfg(target_os = "android")] in Rust werken vergelijkbaar met Swift-directives: de controle vindt plaats op compilerniveau, niet op preprocessorniveau. Dit maakt Rust een aantrekkelijke keuze voor native bibliotheken die vanuit één codebase voor Android en iOS moeten worden gecompileerd.

Praktische scenario’s en antipatronen

Conditionele compilatie is effectief in strikt gedefinieerde scenario’s. Bij onjuist gebruik creëert het code met een geur die moeilijk te testen en te onderhouden is. Laten we de correcte scenario’s en typische fouten bekijken.

Correcte scenario’s

Het eerste scenario — platformabstractie: een uniforme facade, waarbinnen conditionele compilatie de platformimplementatie selecteert. Het tweede — debuggen en profileren: ontwikkeltools die niet in de release thuishoren. Het derde — achterwaartse compatibiliteit: ondersteuning voor oude besturingssysteemversies totdat de minimale versie wordt bijgewerkt.

ScenarioTaalVoorwaarde
PlatformabstractieSwift#if os(iOS)
DebuggenSwift/ObjC#if DEBUG
Achterwaartse compatibiliteitSwift#if swift(>=5.7)
Native bibliotheekC/C++#ifdef __ANDROID__
A/B-testenJava/KotlinBuildConfig.FLAVOR

Antipatronen

Het gevaarlijkste antipatroon — verspreiding van directives door de hele code. Als elk tweede bestand #if bevat, is dat een signaal dat de architectuur refactoring vereist. De juiste oplossing — platformcode achter protocollen/interfaces plaatsen en Dependency Injection gebruiken.

  • #if in elk bestand — architecturaal antipatroon. Platformcode moet achter protocollen worden geïsoleerd.
  • Geneste #if — worden snel onleesbaar. De nestdiepte mag niet meer dan 2 niveaus bedragen.
  • Duplicatie van hele functies — als een functie volledig is gekopieerd in #if en #else, moet deze naar een gemeenschappelijk deel worden verplaatst.
  • Testen — code in inactieve takken wordt niet getest. CI-builds van alle mogelijke combinaties zijn noodzakelijk.
  • Magische vlaggen — ongedocumenteerde vlaggen waarvan het nieuwe ontwikkelteam niet op de hoogte is.

Veelgestelde vragen

Waarin verschilt conditionele compilatie van runtime-controles?

Conditionele compilatie werkt in de compilatiefase: inactieve code komt niet in het binaire bestand. Runtime-controles (if / switch) worden altijd gecompileerd, de voorwaarde wordt tijdens uitvoering gecontroleerd. De eerste is veiliger en efficiënter, de tweede is flexibeler (kan worden gewijzigd zonder herbouwen).

Kan #if binnen een functie in Swift worden gebruikt?

Ja, Swift staat directives #if binnen functies, lussen en zelfs binnen expressies toe. Dit is een van de mogelijkheden die ontbraken in vroege versies van Swift. Bijvoorbeeld: let x = #if DEBUG 1 #else 0 #endif — correcte code.

Waarom heeft Kotlin geen preprocessor toegevoegd?

De ontwikkelaars van Kotlin hebben bewust afgezien van een preprocessor, omdat ze het beschouwen als een bron van breekbare code. In plaats daarvan bieden ze expect/actual (compile-time veiligheid) en Gradle sourceSets (isolatie op bestandsniveau). Beide benaderingen zijn betrouwbaarder dan tekstuele vervanging.

Hoe test je code in inactieve #if-takken?

Bouw de applicatie met verschillende combinaties van vlaggen in CI. Voor Swift: stel aparte Xcode-schema’s in met verschillende Active Compilation Conditions. Voor Android: stel aparte Build Variants in en voer tests uit voor elke variant. Automatisering is verplicht.

Wat gebeurt er als de #if-voorwaarde een syntaxisfout bevat?

In Swift is de #if-voorwaarde een compilerdirective. Als de voorwaarde zelf syntactisch onjuist is (bijvoorbeeld een typefout in de naam os()), geeft de compiler een compilatiefout. In C/C++ vindt de preprocessor de macro gewoon niet en wordt de voorwaarde onwaar.

Samenvatting

  • Conditionele compilatie — compilatietechniek die niet-doelgerichte code in de buildfase uitsluit, in tegenstelling tot runtime-controles.
  • Swift ondersteunt #if met os(), canImport(), swift() — compile-time veilige directives die syntactische correctheid van alle takken vereisen.
  • Kotlin gebruikt expect/actual en Gradle sourceSets in plaats van een preprocessor — betrouwbaardere maar minder flexibele benaderingen.
  • C/C++ in NDK gebruikt de klassieke tekstuele preprocessor #ifdef / #ifndef met platformmacro’s __ANDROID__, __APPLE__.
  • Correct gebruik — platformabstractie, debuggen, achterwaartse compatibiliteit. Onjuist gebruik — #if in elk bestand, diepe nesting, magische vlaggen.
  • CI is verplicht — alle combinaties van vlaggen moeten automatisch worden gebouwd en getest, anders wordt code in inactieve takken dood.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook