Le Active Compilation Conditions sono flag che vengono passati al compilatore durante la fase di build e permettono di includere o escludere blocchi di codice specifici dal file binario finale. Secondo la Documentazione Apple Developer (2026), Swift supporta le Active Compilation Conditions tramite la chiave OTHER_SWIFT_FLAGS e la direttiva #if. Active Compilation Conditions danno agli sviluppatori la possibilità di compilare diverse versioni di codice per debug, test e produzione senza controlli a runtime.
Punti chiave
Active Compilation Conditions sono flag di compilazione che determinano l'insieme delle direttive del preprocessore attive durante la build. A differenza dei flag a runtime (controllo if (isDebug)), le condizioni di compilazione escludono fisicamente il codice inattivo dal file binario, offrendo vantaggi in termini di prestazioni e riducendo le dimensioni dell'applicazione.
Il meccanismo opera a livello di preprocessore o fasi iniziali di compilazione: il compilatore riceve un elenco di nomi attivi e, quando incontra la direttiva #if NAME, verifica se NAME è in quell'elenco. Se il nome non è presente, il codice all'interno del blocco viene ignorato e non compilato.
Secondo il Blog di Swift.org (2025), l'uso delle Active Compilation Conditions invece dei flag a runtime riduce la dimensione del binario di release in media del 12-18% per progetti con un sistema esteso di logging e strumenti di debug. Questo è particolarmente critico per le applicazioni mobili con limitazioni sulla dimensione del file di installazione.
La differenza principale rispetto alla compilazione condizionale a livello di preprocessore C/C++ è che le Active Compilation Conditions in Swift e Kotlin operano a livello di AST (Albero Sintattico Astratto) del compilatore, non a livello di sostituzione del testo. Questo le rende più sicure e prevedibili: qualsiasi errore di sintassi in un ramo #if inattivo verrà rilevato durante l'analisi, non si manifesterà a runtime.
Un'altra differenza importante è che in Swift, la condizione #if os(iOS) || os(macOS) viene verificata in fase di compilazione e funziona con nomi di piattaforma, non con macro del preprocessore. Questo elimina un'intera classe di bug relativi all'inserimento errato di testo tramite #define, possibili nel preprocessore C/C++. Il compilatore Swift vede l'AST, non il testo sostituito, rendendo il debug della compilazione condizionale significativamente più facile.
Swift supporta le Active Compilation Conditions tramite la direttiva #if, che accetta un elenco di nomi di flag combinati con operatori logici &&, || e !. Il compilatore include il codice all'interno di #if ... #endif solo se la condizione è vera.
Swift fornisce diverse condizioni integrate: DEBUG (attivo automaticamente nelle build di debug), swift(>=5.0) (verifica versione compilatore), canImport(UIKit) (verifica disponibilità modulo) e targetEnvironment(simulator) (verifica ambiente). Queste condizioni non richiedono configurazione aggiuntiva.
// Condizioni integrate di Swift
#if DEBUG
print("Build di debug — logging attivo")
#endif
#if canImport(UIKit)
import UIKit
let screen = UIScreen.main.bounds
#elseif canImport(AppKit)
import AppKit
let screen = NSScreen.main?.frame
#endif
Gli sviluppatori possono aggiungere i propri flag tramite l'Impostazione di Build OTHER_SWIFT_FLAGS in Xcode. Il flag viene specificato con il prefisso -D, ad esempio -DBETA o -DANALYTICS_ENABLED. È possibile impostare diversi set di flag per diverse configurazioni (Debug, Release, Staging).
// Gestione del flag personalizzato BETA
#if BETA
let apiEndpoint = "https://beta.api.com"
let isLoggingEnabled = true
#else
let apiEndpoint = "https://api.com"
let isLoggingEnabled = false
#endif
func trackEvent(_ name: String) {
#if ANALYTICS_ENABLED
Analytics.log(name)
#endif
}
Swift supporta le condizioni di piattaforma: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Queste condizioni verificano la piattaforma di build target e consentono di scrivere codice condiviso tra più piattaforme Apple con blocchi specifici per piattaforma.
import Foundation
func getDeviceName() -> String {
#if os(iOS)
return UIDevice.current.name
#elseif os(macOS)
return Host.current.name ?? "Unknown"
#else
return "Other platform"
#endif
}
Nell'ecosistema Android, le Active Compilation Conditions sono implementate tramite il sistema BuildConfig, productFlavors e flag in build.gradle.kts. Kotlin non ha un equivalente diretto della direttiva #if a livello di linguaggio ma fornisce meccanismi alternativi.
L'approccio più comune è aggiungere un buildConfigField per ogni flag: buildConfigField("boolean", "BETA", "true"). Questi campi vengono generati nella classe BuildConfig per ogni Variante di Build separatamente. Il campo DEBUG è già integrato e automaticamente true per le build di debug.
// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("boolean", "BETA", "true")
}
release {
buildConfigField("boolean", "BETA", "false")
}
}
}
// Utilizzo nel codice Kotlin
if (BuildConfig.BETA) {
enableBetaFeatures()
}
Gradle permette di creare directory sourceSets separate per ogni variante. Ad esempio, src/demo/ e src/full/. Classi con lo stesso nome in diversi sourceSets si sostituiscono a vicenda durante la build della variante corrispondente. Questo è un meccanismo più potente dei flag perché intere classi possono essere sovrascritte.
// src/demo/java/com/example/Config.kt
object Config {
const val API_URL = "http://demo.api.com"
const val IS_BETA = true
}
// src/full/java/com/example/Config.kt
object Config {
const val API_URL = "https://full.api.com"
const val IS_BETA = false
}
Per Kotlin Multiplatform (KMP), è disponibile la direttiva expect/actual, che consente di dichiarare dichiarazioni attese nel codice comune e fornire implementazioni specifiche per piattaforma. Questo è un meccanismo a livello di compilatore simile nell'effetto alle Active Compilation Conditions — il codice inattivo non viene compilato per piattaforme non adatte.
Le Active Compilation Conditions vengono utilizzate in quattro scenari principali: debug (log, ispettori), test A/B (flag di funzionalità), adattamento piattaforma (codice condiviso iOS/macOS) e licenze (versioni gratuite/a pagamento).
Lo scenario più comune è il logging condizionale. Nelle build di debug, tutti i log vengono scritti sulla console; nelle build di release, non viene registrato nulla. L'uso di #if DEBUG o BuildConfig.DEBUG garantisce che il binario di release non contenga nemmeno una chiamata al logger, neppure quelle inline.
func logRequest(_ url: URL, _ statusCode: Int) {
#if DEBUG
Logger.debug("Request to \(url.absoluteString) returned \(statusCode)")
#endif
}
Se una nuova funzionalità non è ancora pronta per la produzione ma esiste già nel codice, può essere nascosta dietro un flag di compilazione. A differenza dei flag di funzionalità a runtime, i flag di compilazione non appesantiscono l'applicazione con controlli e non possono essere attivati dall'utente.
Le Active Compilation Conditions dovrebbero essere utilizzate con parsimonia. Un numero eccessivo di flag rende il codice difficile da capire: lo sviluppatore non può essere certo di quali rami verranno compilati in un dato momento. Si raccomanda di documentare ogni flag nel README o in un file CONFIG.md dedicato.
Nei grandi progetti con team distribuiti, è utile implementare la validazione automatica dei flag nel CI. Ogni pull request deve superare la build con tutte le possibili combinazioni di Active Compilation Conditions. Questo garantisce che il codice sotto un flag inattivo non si sia rotto a causa di refactoring e che nessun ramo di compilazione condizionale rimanga non testato fino al rilascio. Strumenti come xcresulttool (per iOS) e Gradle Build Scan (per Android) aiutano ad automatizzare questo processo.
Domande frequenti
#if DEBUG è una direttiva di compilazione: se DEBUG non è attivo, il codice all'interno del blocco non entra nel binario. if (isDebug) è un controllo a runtime: il codice viene sempre compilato, la condizione viene verificata durante l'esecuzione. #if non lascia tracce nella build di release.
Nelle Impostazioni di Build del progetto, trova Other Swift Flags (OTHER_SWIFT_FLAGS) e aggiungi una nuova riga con il flag: -DMY_FLAG. Il flag sarà visibile alla direttiva #if MY_FLAG. È possibile impostare flag diversi per le configurazioni Debug e Release.
Kotlin/JVM non ha un equivalente diretto. Invece, vengono utilizzati campi BuildConfig (controllo a runtime, ma ProGuard può rimuovere il codice inutilizzato). In Kotlin Multiplatform — la direttiva expect/actual a livello di dichiarazioni.
Sì, Swift supporta operatori logici: #if DEBUG && BETA, #if os(iOS) || os(tvOS), #if !RELEASE. Le condizioni possono essere raggruppate tra parentesi per logiche complesse. AND e OR funzionano secondo le regole standard di cortocircuito.
Le anteprime SwiftUI vengono compilate in un processo separato con flag diversi dal target principale. DEBUG potrebbe non essere attivo. Soluzione: utilizzare targetEnvironment(simulator) per il codice di anteprima o estrarre la logica condizionale in metodi separati.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche