Compilarea condiționată în aplicațiile mobile — esența, directivele și principiul de funcționare

Autor: IT Sectr Publicat: 2026-06-01 Timp de citire: 9 min

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ă — tehnica de compilare selectivă a codului în funcție de condițiile platformei, configurației sau versiunii limbajului.
  • Directivele #if, #elseif, #else, #endif — construcțiile de bază ale compilării condiționate în Swift, C, C++, Objective-C.
  • Kotlin nu are directive de preprocesor — în locul lor se folosesc BuildConfig, expect/actual și sourceSets.
  • Avantajul — codul pentru platformele nepotrivite nu este compilat, reducând dimensiunea binarului și eliminând erorile.
  • iOS/macOS cod comun — compilarea condiționată este baza dezvoltării framework-urilor cross-platform Apple.

Ce este compilarea condiționată

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.

Compilarea condiționată în Swift

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.

Verificări de platformă os()

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.

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

Verificarea versiunii compilatorului

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.

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

Verificarea disponibilității modulului canImport()

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.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Implementarea feedback-ului tactil
        }
    }
#endif

Alternative în Kotlin și Android

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

Source Sets în Gradle

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.

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 în release
    }
}

Expect/Actual în Kotlin Multiplatform

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.

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

Preprocesorul C/C++ și NDK

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

Steaguri de platformă NDK

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

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

Scenarii practice și anti-pattern-uri

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.

Scenarii corecte

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

ScenariuLimbajCondiție
Abstractizarea platformeiSwift#if os(iOS)
DebuggingSwift/ObjC#if DEBUG
Compatibilitate inversăSwift#if swift(>=5.7)
Bibliotecă nativăC/C++#ifdef __ANDROID__
Testare A/BJava/KotlinBuildConfig.FLAVOR

Anti-pattern-uri

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.

  • #if în fiecare fișier — anti-pattern arhitectural. Codul de platformă trebuie izolat în spatele protocoalelor.
  • #if îmbricate — devin rapid ilizibile. Adâncimea de îmbricare nu trebuie să depășească 2 niveluri.
  • Duplicarea întregilor funcții — dacă o funcție este copiată complet în #if și #else, trebuie extrasă într-o parte comună.
  • Testarea — codul din ramurile inactive nu este testat. Sunt necesare construiri CI ale tuturor combinațiilor posibile.
  • Steaguri magice — steaguri nedocumentate despre care noua echipă de dezvoltatori nu știe.

Întrebări frecvente

Cu ce diferă compilarea condiționată de verificările la runtime?

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

Se poate folosi #if în interiorul unei funcții în Swift?

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.

De ce Kotlin nu a adăugat preprocesor?

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

Cum se testează codul din ramurile inactive #if?

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.

Ce se întâmplă dacă condiția #if conține o eroare sintactică?

Î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

  • Compilarea condiționată — tehnica de compilare care exclude codul nețintă la etapa de construire, spre deosebire de verificările la runtime.
  • Swift suportă #if cu os(), canImport(), swift() — directive sigure la compilare care necesită corectitudine sintactică a tuturor ramurilor.
  • Kotlin folosește expect/actual și Gradle sourceSets în loc de preprocesor — abordări mai fiabile, dar mai puțin flexibile.
  • C/C++ în NDK folosește preprocesorul textual clasic #ifdef / #ifndef cu macro-urile de platformă __ANDROID__, __APPLE__.
  • Utilizarea corectă — abstractizarea platformei, debugging, compatibilitate inversă. Utilizarea incorectă — #if în fiecare fișier, îmbricare profundă, steaguri magice.
  • CI este obligatoriu — toate combinațiile de steaguri trebuie construite și testate automat, altfel codul din ramurile inactive devine mort.

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.

Discutați proiectul

Citiți și