Compilarea condiționată permite compilatorului să includă sau să omită părți din codul sursă în funcție de condiții cunoscute în faza de construire. Potrivit The Swift Programming Language (2026), directiva #if este procesată în etapa de analiză AST înainte de generarea codului mașină. Compilarea condiționată oferă dezvoltatorilor posibilitatea de a menține o bază de cod unică pentru mai multe platforme și configurații fără duplicare.
Principalele
Compilarea condiționată este un mecanism prin care compilatorul analizează directivele de compilare condiționată și include în fișierul binar de ieșire doar acele blocuri de cod pentru care condițiile sunt îndeplinite. Acest lucru permite existența unei baze de cod unice care se adaptează la diferite platforme țintă și configurații.
Conceptul provine din C/C++ cu directivele de preprocesor #ifdef, #ifndef, #endif. În limbajele moderne (Swift, Rust, Go) mecanismul funcționează la nivel de compilator fără un preprocesor separat, ceea ce sporește siguranța: blocurile condiționate trebuie să fie corecte sintactic, chiar dacă nu sunt compilate.
Potrivit datelor sesiunii Apple WWDC „Embrace Swift” (2025), aproximativ 40% dintre proiectele Swift folosesc compilarea condiționată pentru a suporta iOS și macOS într-un singur target. Pentru proiectele cu UIKit și SwiftUI, codul UI este adesea separat prin directivele #if os(iOS) și #if os(macOS), ceea ce permite reutilizarea logicii de afaceri.
Principalul avantaj — siguranța la compilare. Codul pentru platforma nepotrivită nu doar că nu se execută, ci nici nu se compilează. Asta înseamnă că erorile din codul specific iOS nu se vor manifesta la construirea pentru macOS și invers. Verificările la runtime nu oferă o astfel de garanție.
Swift oferă patru directive cheie: #if, #elseif, #else, #endif. Spre deosebire de preprocesorul C, Swift necesită corectitudinea sintactică a codului în toate ramurile — compilatorul parsează tot codul, dar generează cod mașină doar pentru ramurile active.
Swift suportă funcții de verificare încorporate: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Aceste funcții verifică platforma țintă pentru care se construiește aplicația. Combinarea cu && și || permite crearea de condiții complexe.
// Cod unic pentru iOS, macOS și 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 suportă verificarea versiunii compilatorului: #if swift(>=5.9). Acest lucru este util pentru bibliotecile și framework-urile care suportă mai multe versiuni Swift. Noile capacități ale limbajului (de exemplu, macro-urile în Swift 5.9) pot fi protejate printr-o astfel de verificare.
// Compatibilitate inversă
#if swift(>=5.9)
@MainActor
struct ModernView: View {
var body: some View {
Text("SwiftUI modern")
}
}
#else
struct ModernView: View {
var body: some View {
Text("SwiftUI moștenit")
}
}
#endif
Funcția canImport(ModuleName) verifică dacă modulul specificat este disponibil în mediul curent de construire. Acesta este cel mai flexibil mecanism: nu este legat de o platformă specifică. De exemplu, codul care folosește CoreHaptics va fi compilat doar pe dispozitivele unde acest framework este disponibil.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Implementarea feedback-ului tactil
}
}
#endif
Kotlin ca limbaj nu are directive de preprocesor. În schimb, ecosistemul Android oferă trei alternative: câmpurile BuildConfig (verificări la runtime), sourceSets (înlocuirea întregilor fișiere) și expect/actual (în Kotlin Multiplatform).
Gradle sourceSets permit existența unor implementări diferite ale claselor pentru diferite variante sau tipuri de construire. În directorul src/debug/ se află implementarea pentru debug, în src/release/ — pentru release. La construire, Gradle selectează sourceSet-ul corespunzător și compilează doar fișierele acestuia.
// 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 în release
}
}
KMP oferă mecanismul expect (declarație în codul comun) și actual (implementare pentru o platformă specifică). Acesta este un mecanism la compilare: pentru iOS se compilează implementarea actual din iOS sourceSet, pentru Android — din Android sourceSet. Implementările nețintă nu sunt compilate.
// commonMain — declarație expect
expect fun getPlatformName(): String
// androidMain — actual pentru Android
actual fun getPlatformName(): String =
"Android \${Build.VERSION.SDK_INT}"
// iosMain — actual pentru iOS
actual fun getPlatformName(): String =
UIDevice.current.systemName() + " " + UIDevice.current.systemVersion
în dezvoltarea bibliotecilor native prin Android NDK se folosește preprocesorul clasic C/C++ cu directivele #ifdef, #ifndef, #define. Spre deosebire de Swift, preprocesorul C funcționează la nivel textual — codul din ramurile inactive poate fi incorect sintactic.
NDK definește macro-uri pentru fiecare platformă: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Pentru arhitecturi: __arm__, __aarch64__, __x86_64__. Aceste macro-uri sunt setate automat de compilator la construirea pentru platforma țintă.
// Cod nativ pentru Android și 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
Când lucrați cu NDK, este important să rețineți că preprocesorul C/C++ este o înlocuire textuală. Dacă în ramura inactivă există o eroare sintactică, compilatorul nu o va vedea, dar dacă din cauza unui #define incorect ramura activă se strică — eroarea se va manifesta. Se recomandă minimizarea lanțurilor de #define și utilizarea constantelor constexpr.
Pentru Rust, care este de asemenea folosit în dezvoltarea mobilă prin UniFFI și Mozilla Application Services, există propriul mecanism — feature flags în Cargo.toml. Steaguri precum #[cfg(target_os = "android")] în Rust funcționează similar directivelor Swift: verificarea se face la nivel de compilator, nu de preprocesor. Acest lucru face din Rust o alegere atractivă pentru bibliotecile native care trebuie să se compileze pentru Android și iOS dintr-o singură bază de cod.
Compilarea condiționată este eficientă în scenarii strict definite. Când este utilizată incorect, creează cod cu miros care este dificil de testat și întreținut. Să analizăm scenariile corecte și greșelile tipice.
Primul scenariu — abstractizarea platformei: o fațadă unică, în interiorul căreia compilarea condiționată selectează implementarea platformei. Al doilea — debugging și profilare: instrumente de dezvoltare care nu ar trebui să ajungă în versiunea finală. Al treilea — compatibilitate inversă: suport pentru versiuni vechi de sistem de operare până când versiunea minimă este actualizată.
| Scenariu | Limbaj | Condiție |
|---|---|---|
| Abstractizarea platformei | Swift | #if os(iOS) |
| Debugging | Swift/ObjC | #if DEBUG |
| Compatibilitate inversă | Swift | #if swift(>=5.7) |
| Bibliotecă nativă | C/C++ | #ifdef __ANDROID__ |
| Testare A/B | Java/Kotlin | BuildConfig.FLAVOR |
Cel mai periculos anti-pattern — împraștierea directivelor în tot codul. Dacă fiecare al doilea fișier conține #if, acesta este un semnal că arhitectura necesită refactorizare. Soluția corectă — separarea codului de platformă în spatele protocoalelor/interfețelor și utilizarea Dependency Injection.
Întrebări frecvente
Compilarea condiționată funcționează la etapa de compilare: codul inactiv nu ajunge în binar. Verificările la runtime (if / switch) se compilează întotdeauna, condiția este verificată în timpul execuției. Prima este mai sigură și mai eficientă, a doua este mai flexibilă (poate fi schimbată fără recompilare).
Da, Swift permite directivele #if în interiorul funcțiilor, buclelor și chiar în interiorul expresiilor. Aceasta este una dintre capacitățile care lipseau în versiunile timpurii Swift. De exemplu: let x = #if DEBUG 1 #else 0 #endif — cod corect.
Dezvoltatorii Kotlin au renunțat în mod conștient la preprocesor, considerându-l o sursă de cod fragil. În schimb, ei oferă expect/actual (siguranță la compilare) și Gradle sourceSets (izolare la nivel de fișiere). Ambele abordări sunt mai fiabile decât înlocuirea textuală.
Construiți aplicația cu diferite combinații de steaguri în CI. Pentru Swift: configurați scheme Xcode separate cu diferite Active Compilation Conditions. Pentru Android: configurați Build Variants separate și rulați teste pentru fiecare. Automatizarea este obligatorie.
În Swift, condiția #if este o directivă de compilator. Dacă însuși condiția este incorectă sintactic (de exemplu, o greșeală de tastare în numele os()), compilatorul va emite o eroare de compilare. În C/C++, preprocesorul pur și simplu nu va găsi macro-ul și condiția va deveni falsă.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și