Villkorlig kompilering gör det möjligt för kompilatorn att inkludera eller hoppa över delar av källkoden beroende på villkor som är kända i byggfasen. Enligt The Swift Programming Language (2026) bearbetas #if-direktivet i AST-analysfasen före generering av maskinkod. Villkorlig kompilering ger utvecklare möjligheten att underhålla en enhetlig kodbas för flera plattformar och konfigurationer utan duplicering.
Huvudsakliga
Villkorlig kompilering är en mekanism där kompilatorn analyserar direktiv för villkorlig kompilering och inkluderar i den utgående binärfilen endast de kodblock vars villkor är uppfyllda. Detta gör det möjligt att ha en enhetlig kodbas som anpassar sig till olika målplattformar och konfigurationer.
Konceptet kommer från C/C++ med preprocessordirektiven #ifdef, #ifndef, #endif. I moderna språk (Swift, Rust, Go) fungerar mekanismen på kompilatornivå utan en separat preprocessor, vilket ökar säkerheten: villkorliga block måste vara syntaktiskt korrekta, även om de inte kompileras.
Enligt data från Apple WWDC-sessionen ”Embrace Swift” (2025) använder cirka 40% av Swift-projekten villkorlig kompilering för att stödja iOS och macOS i ett mål. För projekt med UIKit och SwiftUI separeras UI-koden ofta med direktiven #if os(iOS) och #if os(macOS), vilket möjliggör återanvändning av affärslogik.
Den främsta fördelen — säkerhet vid kompileringstid. Kod för en olämplig plattform körs inte bara inte, utan kompileras inte heller. Detta innebär att fel i iOS-specifik kod inte kommer att visa sig vid byggning för macOS och vice versa. Körningskontroller ger inte en sådan garanti.
Swift tillhandahåller fyra viktiga direktiv: #if, #elseif, #else, #endif. Till skillnad från C-preprocessorn kräver Swift syntaktisk korrekthet av kod i alla grenar — kompilatorn tolkar all kod men genererar maskinkod endast för aktiva grenar.
Swift stöder inbyggda kontrollfunktioner: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Dessa funktioner kontrollerar målplattformen som applikationen byggs för. Kombination med && och || gör det möjligt att skapa komplexa villkor.
// Enhetlig kod för iOS, macOS och 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 stöder kontroll av kompilatorversion: #if swift(>=5.9). Detta är användbart för bibliotek och ramverk som stöder flera Swift-versioner. Nya språkmöjligheter (till exempel makron i Swift 5.9) kan skyddas med en sådan kontroll.
// Bakåtkompatibilitet
#if swift(>=5.9)
@MainActor
struct ModernView: View {
var body: some View {
Text("Modern SwiftUI")
}
}
#else
struct ModernView: View {
var body: some View {
Text("Äldre SwiftUI")
}
}
#endif
Funktionen canImport(ModuleName) kontrollerar om den angivna modulen är tillgänglig i den aktuella byggmiljön. Detta är den mest flexibla mekanismen: den är inte bunden till en specifik plattform. Till exempel kommer kod som använder CoreHaptics endast att kompileras på enheter där detta ramverk är tillgängligt.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Implementering av taktil återkoppling
}
}
#endif
Kotlin som språk har inga preprocessordirektiv. Istället erbjuder Android-ekosystemet tre alternativ: BuildConfig-fält (körningskontroller), sourceSets (ersättning av hela filer) och expect/actual (i Kotlin Multiplatform).
Gradle sourceSets gör det möjligt att ha olika implementeringar av klasser för olika varianter eller byggtyper. I katalogen src/debug/ finns implementeringen för debug, i src/release/ — för release. Vid byggning väljer Gradle lämplig sourceSet och kompilerar endast dess filer.
// 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 i release
}
}
KMP tillhandahåller mekanismen expect (deklaration i delad kod) och actual (implementering för en specifik plattform). Detta är en kompileringstidsmekanism: för iOS kompileras actual-implementeringen från iOS sourceSet, för Android — från Android sourceSet. Icke-målinriktade implementeringar kompileras inte.
// commonMain — expect-deklaration
expect fun getPlatformName(): String
// androidMain — actual för Android
actual fun getPlatformName(): String =
"Android \${Build.VERSION.SDK_INT}"
// iosMain — actual för iOS
actual fun getPlatformName(): String =
UIDevice.current.systemName() + " " + UIDevice.current.systemVersion
Vid utveckling av inbyggda bibliotek via Android NDK används den klassiska C/C++-preprocessorn med direktiven #ifdef, #ifndef, #define. Till skillnad från Swift arbetar C-preprocessorn på textnivå — kod i inaktiva grenar kan vara syntaktiskt inkorrekt.
NDK definierar makron för varje plattform: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). För arkitekturer: __arm__, __aarch64__, __x86_64__. Dessa makron ställs in automatiskt av kompilatorn vid byggning för målplattformen.
// Inbyggd kod för Android och 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
När du arbetar med NDK är det viktigt att komma ihåg att C/C++-preprocessorn är en textersättning. Om det finns ett syntaxfel i en inaktiv gren kommer kompilatorn inte att se det, men om den aktiva grenen skadas på grund av felaktig #define — kommer felet att visas. Det rekommenderas att minimera #define-kedjor och använda constexpr-konstanter.
För Rust, som också används i mobilutveckling via UniFFI och Mozilla Application Services, finns en egen mekanism — feature flags i Cargo.toml. Flaggor som #[cfg(target_os = "android")] i Rust fungerar liknande Swift-direktiv: kontrollen sker på kompilatornivå, inte preprocessornivå. Detta gör Rust till ett attraktivt val för inbyggda bibliotek som måste kompileras för Android och iOS från en enda kodbas.
Villkorlig kompilering är effektiv i strikt definierade scenarier. Vid felaktig användning skapar den luktande kod som är svår att testa och underhålla. Låt oss titta på korrekta scenarier och typiska misstag.
Första scenariot — plattformsabstraktion: en enhetlig fasad, inom vilken villkorlig kompilering väljer plattformsimplementeringen. Andra — felsökning och profilering: utvecklarverktyg som inte bör hamna i releasen. Tredje — bakåtkompatibilitet: stöd för gamla operativsystemversioner tills minimiversionen uppdateras.
| Scenario | Språk | Villkor |
|---|---|---|
| Plattformsabstraktion | Swift | #if os(iOS) |
| Felsökning | Swift/ObjC | #if DEBUG |
| Bakåtkompatibilitet | Swift | #if swift(>=5.7) |
| Inbyggt bibliotek | C/C++ | #ifdef __ANDROID__ |
| A/B-testning | Java/Kotlin | BuildConfig.FLAVOR |
Det farligaste antimönstret — spridning av direktiv i hela koden. Om varannan fil innehåller #if är det en signal att arkitekturen kräver omfaktorisering. Den korrekta lösningen — separera plattformskoden bakom protokoll/gränssnitt och använd Dependency Injection.
Vanliga frågor
Villkorlig kompilering fungerar i kompileringsfasen: inaktiv kod kommer inte in i binärfilen. Körningskontroller (if / switch) kompileras alltid, villkoret kontrolleras under körning. Det första är säkrare och effektivare, det andra är mer flexibelt (kan ändras utan omkompilering).
Ja, Swift tillåter direktiv #if inuti funktioner, loopar och även inuti uttryck. Detta är en av funktionerna som saknades i tidiga versioner av Swift. Till exempel: let x = #if DEBUG 1 #else 0 #endif — korrekt kod.
Utvecklarna av Kotlin avstod medvetet från preprocessorn, eftersom de anser den vara en källa till skör kod. Istället erbjuder de expect/actual (kompileringstidssäkerhet) och Gradle sourceSets (isolering på filnivå). Båda metoderna är mer tillförlitliga än textersättning.
Bygg applikationen med olika flaggkombinationer i CI. För Swift: konfigurera separata Xcode-scheman med olika Active Compilation Conditions. För Android: konfigurera separata Build Variants och kör tester för varje. Automatisering är obligatorisk.
I Swift är #if-villkoret ett kompilatordirektiv. Om själva villkoret är syntaktiskt inkorrekt (till exempel ett stavfel i namnet os()) kommer kompilatorn att utfärda ett kompileringsfel. I C/C++ hittar preprocessorn helt enkelt inte makrot och villkoret blir falskt.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också