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 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.
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.
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.
// 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
}
}
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.
// 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
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.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Implementatie van tactiele feedback
}
}
#endif
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).
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.
// 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
}
}
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.
// 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
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 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.
// 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.
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.
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.
| Scenario | Taal | Voorwaarde |
|---|---|---|
| Platformabstractie | Swift | #if os(iOS) |
| Debuggen | Swift/ObjC | #if DEBUG |
| Achterwaartse compatibiliteit | Swift | #if swift(>=5.7) |
| Native bibliotheek | C/C++ | #ifdef __ANDROID__ |
| A/B-testen | Java/Kotlin | BuildConfig.FLAVOR |
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.
Veelgestelde vragen
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).
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.
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.
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.
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
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.
Lees ook