A feltételes fordítás lehetővé teszi a fordító számára, hogy a forráskód részeit a build fázisban ismert feltételektől függően beillessze vagy kihagyja. A The Swift Programming Language (2026) szerint a #if direktíva az AST elemzés fázisában kerül feldolgozásra a gépi kód generálása előtt. A feltételes fordítás lehetővé teszi a fejlesztők számára, hogy egységes kódbázist tartsanak fenn több platformhoz és konfigurációhoz ismétlés nélkül.
Főbb pontok
Feltételes fordítás — egy mechanizmus, amelyben a fordító elemzi a feltételes fordítás direktíváit, és csak azokat a kódblokkokat illeszti be a kimeneti bináris fájlba, amelyek feltételei teljesülnek. Ez lehetővé teszi egy egységes kódbázis fenntartását, amely alkalmazkodik a különböző célplatformokhoz és konfigurációkhoz.
A koncepció a C/C++-ból származik a #ifdef, #ifndef, #endif preprocesszor direktívákkal. Modern nyelvekben (Swift, Rust, Go) a mechanizmus fordító szintjén működik külön preprocesszor nélkül, ami növeli a biztonságot: a feltételes blokkoknak szintaktikailag helyesnek kell lenniük, még akkor is, ha nem fordulnak le.
Az Apple WWDC „Embrace Swift” (2025) ülésének adatai szerint a Swift projektek körülbelül 40%-a használ feltételes fordítást az iOS és macOS támogatásához egyetlen targetben. UIKit és SwiftUI projekteknél az UI kód gyakran #if os(iOS) és #if os(macOS) direktívákkal van szétválasztva, ami lehetővé teszi az üzleti logika újrafelhasználását.
A fő előny — fordítási időbeni biztonság. A nem megfelelő platformhoz tartozó kód nem csak nem fut le, de nem is fordul le. Ez azt jelenti, hogy az iOS-specifikus kód hibái nem jelennek meg a macOS build során és fordítva. A futásidőbeni ellenőrzések nem adnak ilyen garanciát.
A Swift négy kulcsfontosságú direktívát biztosít: #if, #elseif, #else, #endif. A C preprocesszorral ellentétben a Swift megköveteli a kód szintaktikai helyességét minden ágban — a fordító elemzi az összes kódot, de gépi kódot csak az aktív ágakhoz generál.
A Swift beépített ellenőrző függvényeket támogat: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Ezek a függvények azt a célplatformot ellenőrzik, amelyre az alkalmazást építik. A && és || kombinációja lehetővé teszi összetett feltételek létrehozását.
// Egységes kód iOS, macOS és tvOS számára
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
}
}
A Swift támogatja a fordító verziójának ellenőrzését: #if swift(>=5.9). Ez hasznos olyan könyvtárak és keretrendszerek esetében, amelyek több Swift verziót támogatnak. A nyelv új képességei (például makrók a Swift 5.9-ben) egy ilyen ellenőrzéssel védhetők.
// Visszafelé kompatibilitás
#if swift(>=5.9)
@MainActor
struct ModernView: View {
var body: some View {
Text("Modern SwiftUI")
}
}
#else
struct ModernView: View {
var body: some View {
Text("Örökölt SwiftUI")
}
}
#endif
A canImport(ModuleName) függvény ellenőrzi, hogy a megadott modul elérhető-e a jelenlegi build környezetben. Ez a legrugalmasabb mechanizmus: nem kötődik egy adott platformhoz. Például a CoreHaptics-t használó kód csak azokon az eszközökön fog lefordulni, ahol ez a keretrendszer elérhető.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Tapintási visszajelzés implementációja
}
}
#endif
A Kotlin mint nyelv nem rendelkezik preprocesszor direktívákkal. Ehelyett az Android ökoszisztéma három alternatívát kínál: BuildConfig mezők (futásidőbeni ellenőrzések), sourceSets (teljes fájlok cseréje) és expect/actual (Kotlin Multiplatformban).
A Gradle sourceSets lehetővé teszik, hogy különböző osztályimplementációk legyenek különböző flavor-ekhez vagy build típusokhoz. A src/debug/ könyvtárban található a debug implementáció, a src/release/ könyvtárban — a release. Build során a Gradle kiválasztja a megfelelő sourceSet-et és csak annak fájljait fordítja le.
// 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 release módban
}
}
A KMP expect (deklaráció a közös kódban) és actual (implementáció egy adott platformhoz) mechanizmust biztosít. Ez egy fordítási időbeni mechanizmus: iOS esetén az iOS sourceSet-ből az actual implementáció fordul le, Android esetén — az Android sourceSet-ből. A nem célzott implementációk nem fordulnak le.
// commonMain — expect deklaráció
expect fun getPlatformName(): String
// androidMain — actual Androidhoz
actual fun getPlatformName(): String =
"Android \${Build.VERSION.SDK_INT}"
// iosMain — actual iOS-hez
actual fun getPlatformName(): String =
UIDevice.current.systemName() + " " + UIDevice.current.systemVersion
Natív könyvtárak fejlesztése során az Android NDK-n keresztül a klasszikus C/C++ preprocesszor használatos a #ifdef, #ifndef, #define direktívákkal. A Swift-től eltérően a C preprocesszor szöveges szinten működik — az inaktív ágakban lévő kód szintaktikailag helytelen lehet.
Az NDK makrókat definiál minden platformhoz: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Architektúrákhoz: __arm__, __aarch64__, __x86_64__. Ezeket a makrókat a fordító automatikusan állítja be a célplatformra történő build során.
// Natív kód Androidhoz és iOS-hez
#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
Az NDK-val való munka során fontos megjegyezni, hogy a C/C++ preprocesszor szöveges helyettesítés. Ha egy inaktív ágban szintaktikai hiba van, a fordító nem látja, de ha egy helytelen #define miatt az aktív ág elromlik — a hiba megjelenik. Javasolt a #define láncok minimalizálása és constexpr konstansok használata.
A Rust számára, amelyet szintén használnak mobilfejlesztésben a UniFFI-n és Mozilla Application Services-en keresztül, saját mechanizmus létezik — feature flags a Cargo.toml-ben. Az olyan jelzők, mint a #[cfg(target_os = "android")] a Rust-ban hasonlóan működnek a Swift direktívákhoz: az ellenőrzés fordító szintjén történik, nem preprocesszor szintjén. Ez vonzó választássá teszi a Rust-ot olyan natív könyvtárakhoz, amelyeket egy kódbázisból kell Androidra és iOS-re fordítani.
A feltételes fordítás szigorúan meghatározott forgatókönyvek esetén hatékony. Helytelen használat esetén rossz szagú kódot hoz létre, amelyet nehéz tesztelni és karbantartani. Tekintsük át a helyes forgatókönyveket és a tipikus hibákat.
Első forgatókönyv — platformabsztrakció: egységes homlokzat, amelyen belül a feltételes fordítás kiválasztja a platformimplementációt. Második — hibakeresés és profilozás: fejlesztői eszközök, amelyek nem kerülhetnek a release-be. Harmadik — visszafelé kompatibilitás: régi operációs rendszer verziók támogatása, amíg a minimális verzió frissítésre nem kerül.
| Forgatókönyv | Nyelv | Feltétel |
|---|---|---|
| Platformabsztrakció | Swift | #if os(iOS) |
| Hibakeresés | Swift/ObjC | #if DEBUG |
| Visszafelé kompatibilitás | Swift | #if swift(>=5.7) |
| Natív könyvtár | C/C++ | #ifdef __ANDROID__ |
| A/B tesztelés | Java/Kotlin | BuildConfig.FLAVOR |
A legveszélyesebb antiminta — direktívák elterjedése a kódban. Ha minden második fájl tartalmaz #if-et, az annak a jele, hogy az architektúra refaktorálást igényel. A helyes megoldás — a platformkódot protokollok/interfészek mögé helyezni és Dependency Injection-t használni.
Gyakran Ismételt Kérdések
A feltételes fordítás a fordítás fázisában működik: az inaktív kód nem kerül a binárisba. A futásidőbeni ellenőrzések (if / switch) mindig lefordulnak, a feltétel végrehajtás közben ellenőrződik. Az első biztonságosabb és hatékonyabb, a második rugalmasabb (újrafordítás nélkül változtatható).
Igen, a Swift engedélyezi a #if direktívát függvényeken belül, ciklusokban és akár kifejezéseken belül is. Ez az egyik olyan képesség, amely hiányzott a Swift korai verzióiból. Például: let x = #if DEBUG 1 #else 0 #endif — helyes kód.
A Kotlin fejlesztői tudatosan mondtak le a preprocesszorról, mivel azt a törékeny kód forrásának tartják. Ehelyett expect/actual (fordítási időbeni biztonság) és Gradle sourceSets (fájlszintű izoláció) lehetőségeket kínálnak. Mindkét megközelítés megbízhatóbb, mint a szöveges helyettesítés.
Az alkalmazást különböző jelzőkombinációkkal kell buildelni CI-ben. Swift esetén: külön Xcode sémák konfigurálása különböző Active Compilation Conditions-szel. Android esetén: külön Build Variants konfigurálása és tesztek futtatása mindegyikre. Az automatizálás kötelező.
Swiftben a #if feltétel egy fordítói direktíva. Ha maga a feltétel szintaktikailag helytelen (például elírás az os() nevében), a fordító fordítási hibát ad. C/C++-ban a preprocesszor egyszerűen nem találja meg a makrót, és a feltétel hamis lesz.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is