Villkorlig kompilering i mobilappar — innebörd, direktiv och funktionsprincip

Författare: IT Sectr Publicerad: 2026-06-01 Lästid: 9 min

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 — teknik för selektiv kompilering av kod baserat på plattforms-, konfigurations- eller språkversionsvillkor.
  • Direktiv #if, #elseif, #else, #endif — grundläggande konstruktioner för villkorlig kompilering i Swift, C, C++, Objective-C.
  • Kotlin har inga preprocessordirektiv — istället används BuildConfig, expect/actual och sourceSets.
  • Fördel — kod för olämpliga plattformar kompileras inte, vilket minskar binärstorleken och eliminerar fel.
  • iOS/macOS delad kod — villkorlig kompilering är grunden för utveckling av Apples plattformsoberoende ramverk.

Vad är villkorlig kompilering

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.

Villkorlig kompilering i Swift

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.

Plattformskontroller os()

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.

swift
// 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
    }
}

Kontroll av kompilatorversion

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.

swift
// 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

Kontroll av modultillgänglighet canImport()

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.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Implementering av taktil återkoppling
        }
    }
#endif

Alternativ i Kotlin och Android

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).

Source Sets i Gradle

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.

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

Expect/Actual i Kotlin Multiplatform

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.

kotlin
// 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

C/C++-preprocessorn och NDK

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-plattformsflaggor

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.

cpp
// 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.

Praktiska scenarier och antimönster

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.

Korrekta scenarier

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.

ScenarioSpråkVillkor
PlattformsabstraktionSwift#if os(iOS)
FelsökningSwift/ObjC#if DEBUG
BakåtkompatibilitetSwift#if swift(>=5.7)
Inbyggt bibliotekC/C++#ifdef __ANDROID__
A/B-testningJava/KotlinBuildConfig.FLAVOR

Antimönster

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.

  • #if i varje fil — arkitektoniskt antimönster. Plattformskod bör isoleras bakom protokoll.
  • Nästlade #if — blir snabbt oläsliga. Nästlingsdjupet bör inte överstiga 2 nivåer.
  • Duplicering av hela funktioner — om en funktion är helt kopierad i #if och #else bör den flyttas till en gemensam del.
  • Testning — kod i inaktiva grenar testas inte. CI-byggningar av alla möjliga kombinationer är nödvändiga.
  • Magiska flaggor — odokumenterade flaggor som det nya utvecklarteamet inte känner till.

Vanliga frågor

Vad skiljer villkorlig kompilering från körningskontroller?

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).

Kan #if användas inuti en funktion i Swift?

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.

Varför lade Kotlin inte till en preprocessor?

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.

Hur testar man kod i inaktiva #if-grenar?

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.

Vad händer om #if-villkoret innehåller ett syntaxfel?

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

  • Villkorlig kompilering — kompileringsteknik som exkluderar icke-målinriktad kod i byggfasen, till skillnad från körningskontroller.
  • Swift stöder #if med os(), canImport(), swift() — kompileringstidssäkra direktiv som kräver syntaktisk korrekthet i alla grenar.
  • Kotlin använder expect/actual och Gradle sourceSets istället för preprocessor — mer tillförlitliga men mindre flexibla metoder.
  • C/C++ i NDK använder den klassiska textpreprocessorn #ifdef / #ifndef med plattformsmakron __ANDROID__, __APPLE__.
  • Korrekt användning — plattformsabstraktion, felsökning, bakåtkompatibilitet. Felaktig användning — #if i varje fil, djup nästling, magiska flaggor.
  • CI är obligatoriskt — alla flaggkombinationer måste byggas och testas automatiskt, annars blir kod i inaktiva grenar död.

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.

Diskutera projektet

Läs också