Sostituzione #ifdef nella lingua Swift


734

In C / C ++ / Obiettivo C è possibile definire una macro utilizzando i preprocessori del compilatore. Inoltre, puoi includere / escludere alcune parti di codice usando i preprocessori del compilatore.

#ifdef DEBUG
    // Debug-only code
#endif

Esiste una soluzione simile in Swift?


1
Come idea, potresti metterlo nelle tue intestazioni a ponte obj-c.
Matej

42
Dovresti davvero dare una risposta dato che ne hai diversi tra cui scegliere, e questa domanda ti ha fatto guadagnare un sacco di voti.
David H,

Risposte:


1069

Si, puoi farlo.

In Swift puoi comunque utilizzare le macro del preprocessore "# if / # else / # endif" (anche se più vincolate), secondo i documenti Apple . Ecco un esempio:

#if DEBUG
    let a = 2
#else
    let a = 3
#endif

Ora, però, è necessario impostare il simbolo "DEBUG" altrove. Impostalo nella sezione "Compilatore Swift - Bandiere personalizzate", riga "Altre bandiere Swift". Aggiungi il simbolo DEBUG con la -D DEBUGvoce.

Come al solito, è possibile impostare un valore diverso in Debug o in Release.

L'ho provato in codice reale e funziona; non sembra essere riconosciuto in un parco giochi però.

Puoi leggere il mio post originale qui .


NOTA IMPORTANTE: -DDEBUG=1 non funziona. -D DEBUGFunziona solo . Sembra che il compilatore stia ignorando una bandiera con un valore specifico.


41
Questa è la risposta corretta, sebbene si noti che è possibile verificare solo la presenza della bandiera ma non un valore specifico.
Charles Harley,

19
Nota aggiuntiva : oltre ad aggiungere -D DEBUGcome indicato sopra, è necessario anche definire DEBUG=1in Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros,

38
Non sono riuscito a farlo funzionare fino a quando non ho cambiato la formattazione -DDEBUGda questa risposta: stackoverflow.com/a/24112024/747369 .
Kramer,

11
@MattQuiros Non c'è bisogno di aggiungere DEBUG=1a Preprocessor Macros, se non si desidera utilizzarlo nel codice Objective-C.
derpoliuk,

7
@Daniel Puoi usare operatori booleani standard (es: `#if! DEBUG`)
Jean Le Moignan,

353

Come indicato in Apple Docs

Il compilatore Swift non include un preprocessore. Invece, sfrutta gli attributi in fase di compilazione, le configurazioni di compilazione e le funzionalità del linguaggio per ottenere la stessa funzionalità. Per questo motivo, le direttive del preprocessore non vengono importate in Swift.

Sono riuscito a ottenere quello che volevo usando Configurazioni di costruzione personalizzate:

  1. Vai al tuo progetto / seleziona il tuo obiettivo / Crea impostazioni / cerca bandiere personalizzate
  2. Per il bersaglio scelto imposta il tuo flag personalizzato usando il prefisso -D (senza spazi bianchi), sia per il debug che per il rilascio
  3. Fai i passaggi sopra indicati per ogni obiettivo che hai

Ecco come si controlla l'obiettivo:

#if BANANA
    print("We have a banana")
#elseif MELONA
    print("Melona")
#else
    print("Kiwi")
#endif

inserisci qui la descrizione dell'immagine

Testato utilizzando Swift 2.2


4
1.con il lavoro degli spazi bianchi, 2. dovrebbe impostare la bandiera solo per il debug?
C0ming

3
@ c0ming dipende dalle tue esigenze, ma se vuoi che qualcosa accada solo in modalità debug e non in versione, devi rimuovere -DDEBUG dalla versione.
cdf1982,

1
Dopo aver impostato il flag personalizzato -DLOCAL, sul mio #if LOCAl #else #endif, rientra nella #elsesezione. Ho duplicato il target originale AppTargete lo ho rinominato AppTargetLocale impostato il suo flag personalizzato.
Perwyl Liu,

3
@Andrej ti capita di sapere come far riconoscere anche XCTest ai flag personalizzati? Mi rendo conto che cade nel #if LOCAL risultato previsto quando corro con il simulatore e cade #else durante i test. Voglio che cada #if LOCALanche durante i test.
Perwyl Liu,

3
Questa dovrebbe essere la risposta accettata. La risposta attualmente accettata non è corretta per Swift poiché si applica solo a Objective-C.
miken.mkndev,

171

In molte situazioni, non hai davvero bisogno della compilazione condizionale ; hai solo bisogno di un comportamento condizionale che puoi accendere e spegnere. Per questo, puoi usare una variabile d'ambiente. Questo ha l'enorme vantaggio che in realtà non devi ricompilare.

È possibile impostare la variabile di ambiente e attivarla o disattivarla facilmente nell'editor di schemi:

inserisci qui la descrizione dell'immagine

Puoi recuperare la variabile d'ambiente con NSProcessInfo:

    let dic = NSProcessInfo.processInfo().environment
    if dic["TRIPLE"] != nil {
        // ... do secret stuff here ...
    }

Ecco un esempio di vita reale. La mia app funziona solo sul dispositivo, perché utilizza la libreria musicale, che non esiste sul simulatore. Come, quindi, catturare schermate sul simulatore per dispositivi che non possiedo? Senza quelle schermate, non posso inviare all'App Store.

Ho bisogno di dati falsi e di un modo diverso di elaborarli . Ho due variabili d'ambiente: una che, quando accesa, dice all'app di generare i dati falsi dai dati reali mentre è in esecuzione sul mio dispositivo; l'altro che, quando acceso, utilizza i dati falsi (non la libreria musicale mancante) durante l'esecuzione sul Simulatore. Attivare / disattivare ciascuna di queste modalità speciali è facile grazie alle caselle di controllo delle variabili di ambiente nell'editor Schema. E il vantaggio è che non posso usarli accidentalmente nella build del mio App Store, perché l'archiviazione non ha variabili di ambiente.


Per qualche ragione la mia variabile d'ambiente è tornata a zero al secondo lancio dell'app
Eugene il

60
Attenzione : le variabili di ambiente sono impostate per tutte le configurazioni di build, non possono essere impostate per quelle individuali. Quindi questa non è una soluzione praticabile se è necessario che il comportamento cambi a seconda che si tratti di una versione o di una build di debug.
Eric,

5
@Eric Concordato, ma non sono impostati per tutte le azioni dello schema. Quindi potresti fare una cosa su build-and-run e una cosa diversa su archivio, che è spesso la distinzione nella vita reale che vuoi disegnare. Oppure potresti avere più schemi, che hanno anche uno schema comune nella vita reale. Inoltre, come ho detto nella mia risposta, attivare e disattivare le variabili di ambiente in uno schema è facile.
opaco

10
Le variabili di ambiente NON funzionano in modalità archivio. Vengono applicati solo quando l'app viene avviata da XCode. Se si tenta di accedervi su un dispositivo, l'app andrà in crash. Scoperto nel modo più duro.
iupchris10,

2
@ iupchris10 "L'archiviazione non ha variabili d'ambiente" sono le ultime parole della mia risposta, sopra. Questo, come ho detto nella mia risposta, è buono . È il punto .
matt

160

Un grande cambiamento di ifdefsostituzione è venuto fuori con Xcode 8. vale a dire l'uso delle condizioni di compilazione attiva .

Fare riferimento a Creazione e collegamento nella nota di rilascio di Xcode 8 .

Nuove impostazioni di build

Nuova impostazione: SWIFT_ACTIVE_COMPILATION_CONDITIONS

Active Compilation Conditionsis a new build setting for passing conditional compilation flags to the Swift compiler.

In precedenza, dovevamo dichiarare i flag di compilazione condizionale in OTHER_SWIFT_FLAGS, ricordando di anteporre "-D" all'impostazione. Ad esempio, per compilare in modo condizionale con un valore MYFLAG:

#if MYFLAG1
    // stuff 1
#elseif MYFLAG2
    // stuff 2
#else
    // stuff 3
#endif

Il valore da aggiungere all'impostazione -DMYFLAG

Ora dobbiamo solo passare il valore MYFLAG alla nuova impostazione. È ora di spostare tutti quei valori di compilazione condizionale!

Fai riferimento al link seguente per ulteriori funzionalità di Swift Build Settings in Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


Esiste un modo per disabilitare un set di condizioni di compilazione attive al momento della compilazione? Devo disabilitare la condizione DEBUG durante la creazione della configurazione di debug per i test.
Jonny,

1
@Jonny L'unico modo che ho trovato è creare una terza configurazione di configurazione per il progetto. Da Progetto> scheda Informazioni> Configurazioni, premi '+', quindi duplica Debug. È quindi possibile personalizzare le Condizioni di compilazione attive per questa configurazione. Non dimenticare di modificare il tuo Target> Schemi di test per utilizzare la nuova configurazione di build!
Matthias,

1
Questa dovrebbe essere la risposta corretta ... è l'unica cosa che ha funzionato per me su xCode 9 usando Swift 4.x!
shokaveli,

1
A proposito, in Xcode 9.3 Swift 4.1 DEBUG è già presente nelle condizioni di compilazione attiva e non è necessario aggiungere nulla per verificare la configurazione di DEBUG. Solo #if DEBUG e #endif.
Denis Kutlubaev,

Penso che sia sia fuori tema, sia una brutta cosa da fare. non si desidera disabilitare le condizioni di compilazione attive. hai bisogno di una nuova e diversa configurazione per i test - che NON avrà il tag "Debug" su di esso. Informazioni sugli schemi.
Motti Shneor,

93

A partire da Swift 4.1, se tutto ciò che serve è solo verificare se il codice è stato creato con la configurazione di debug o di rilascio, è possibile utilizzare le funzioni integrate:

  • _isDebugAssertConfiguration()(vero quando l'ottimizzazione è impostata su -Onone)
  • _isReleaseAssertConfiguration()(vero quando l'ottimizzazione è impostata su -O) (non disponibile su Swift 3+)
  • _isFastAssertConfiguration()(vero quando l'ottimizzazione è impostata su -Ounchecked)

per esempio

func obtain() -> AbstractThing {
    if _isDebugAssertConfiguration() {
        return DecoratedThingWithDebugInformation(Thing())
    } else {
        return Thing()
    }
}

Rispetto alle macro del preprocessore,

  • ✓ Non è necessario definire un -D DEBUGflag personalizzato per usarlo
  • ~ In realtà è definito in termini di impostazioni di ottimizzazione, non di configurazione build Xcode
  • ✗ Non documentato, il che significa che la funzione può essere rimossa in qualsiasi aggiornamento (ma dovrebbe essere protetta da AppStore poiché l'ottimizzatore li trasformerà in costanti)

  • ✗ L'uso di in if / else genererà sempre un avviso "Non verrà mai eseguito".


1
Queste funzioni integrate vengono valutate in fase di compilazione o in fase di esecuzione?
ma11hew28,

@MattDiPasquale Tempo di ottimizzazione. if _isDebugAssertConfiguration()verrà valutato if falsein modalità di rilascio ed if trueè la modalità di debug.
kennytm,

2
Tuttavia, non posso usare queste funzioni per disattivare alcune variabili di solo debug in versione.
Franklin Yu,

3
Queste funzioni sono documentate da qualche parte?
Tom Harrington,

7
A partire da Swift 3.0 e XCode 8, queste funzioni non sono valide.
CodeBender,

87

Xcode 8 e versioni successive

Usa l' impostazione Condizioni di compilazione attive in Impostazioni di compilazione / Compilatore rapido - Flag personalizzati .

  • Questa è la nuova impostazione di generazione per il passaggio dei flag di compilazione condizionale al compilatore Swift.
  • Bandiere aggiuntivi semplice come questo: ALPHA, BETAetc.

Quindi verificalo con condizioni di compilazione come questa:

#if ALPHA
    //
#elseif BETA
    //
#else
    //
#endif

Suggerimento: è anche possibile utilizzare #if !ALPHAecc.


77

Non esiste un preprocessore Swift. (Per prima cosa, la sostituzione arbitraria del codice rompe la sicurezza di tipo e memoria.)

Swift include opzioni di configurazione in fase di creazione, tuttavia, in modo da poter includere in modo condizionale il codice per determinate piattaforme o stili di costruzione o in risposta ai flag definiti con gli -Dargomenti del compilatore. A differenza di C, tuttavia, una sezione del codice compilata in modo condizionale deve essere sintatticamente completa. C'è una sezione su questo nell'uso di Swift With Cocoa e Objective-C .

Per esempio:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif

34
"Per prima cosa, la sostituzione arbitraria del codice rompe la sicurezza di tipo e memoria." Un pre-processore non funziona prima del compilatore (da cui il nome)? Quindi tutti questi controlli potrebbero ancora aver luogo.
Thilo,

10
@Thilo Penso che ciò che si rompe sia il supporto IDE
Aleksandr Dubinsky il

1
Penso che @rickster stia arrivando al fatto che le macro del preprocessore C non comprendono il tipo e la loro presenza infrange i requisiti di tipo di Swift. Il motivo per cui le macro funzionano in C è perché C consente la conversione di tipo implicita, il che significa che potresti mettere il tuo INT_CONSTovunque e che floatverrebbe accettato. Swift non lo permetterebbe. Inoltre, se potessi farlo var floatVal = INT_CONSTinevitabilmente, si guasterà da qualche parte in seguito quando il compilatore si aspetta un Intma lo usi come un Float(tipo di floatValsarebbe inferito come Int). 10 lanci più tardi ed è più pulito per rimuovere le macro ...
Ephemera,

Sto cercando di usarlo ma non sembra funzionare, sta ancora compilando il codice Mac su build iOS. C'è un'altra schermata di configurazione da qualche parte che deve essere modificata?
Maury Markowitz,

1
@Thilo hai ragione: un pre-processore non rompe alcun tipo o sicurezza della memoria.
tcurdt

50

I miei due centesimi per Xcode 8:

a) Un flag personalizzato che utilizza il -Dprefisso funziona bene, ma ...

b) Uso più semplice:

In Xcode 8 è presente una nuova sezione: "Condizioni di compilazione attive", già con due righe, per il debug e il rilascio.

Aggiungi semplicemente la tua definizione SENZA -D.


Grazie per aver menzionato che ci sono DUE FILE PER IL DEBUG E IL RILASCIO
Yitzchak,

qualcuno lo ha testato in rilascio?
Glenn,

Questa è la risposta aggiornata per gli utenti veloci. cioè senza -D.
Mani,

46

Costante isDebug basata su condizioni di compilazione attive

Un'altra soluzione, forse più semplice, che si traduce ancora in un valore booleano che puoi passare a funzioni senza peppering #ifcondizionali in tutta la tua base di codice è definire DEBUGcome uno dei tuoi target di costruzione del progetto Active Compilation Conditionse includere quanto segue (lo definisco una costante globale):

#if DEBUG
    let isDebug = true
#else
    let isDebug = false
#endif

Costante isDebug basata sulle impostazioni di ottimizzazione del compilatore

Questo concetto si basa sulla risposta di kennytm

Il vantaggio principale rispetto a kennytm è che non si basa su metodi privati ​​o non documentati.

In Swift 4 :

let isDebug: Bool = {
    var isDebug = false
    // function with a side effect and Bool return value that we can pass into assert()
    func set(debug: Bool) -> Bool {
        isDebug = debug
        return isDebug
    }
    // assert:
    // "Condition is only evaluated in playgrounds and -Onone builds."
    // so isDebug is never changed to true in Release builds
    assert(set(debug: true))
    return isDebug
}()

Rispetto alle macro del preprocessore e alla risposta di kennytm ,

  • ✓ Non è necessario definire un -D DEBUGflag personalizzato per usarlo
  • ~ In realtà è definito in termini di impostazioni di ottimizzazione, non di configurazione build Xcode
  • Documentato , il che significa che la funzione seguirà i normali schemi di rilascio / deprecazione dell'API.

  • ✓ L'uso di in if / else non genererà un avviso "Non verrà mai eseguito".


25

La risposta di Moignans qui funziona bene. Ecco un'altra informazione in caso di aiuto,

#if DEBUG
    let a = 2
#else
    let a = 3
#endif

Puoi annullare le macro come di seguito,

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif

23

Nei progetti Swift creati con Xcode versione 9.4.1, Swift 4.1

#if DEBUG
#endif

funziona di default perché nei macro del preprocessore DEBUG = 1 è già stato impostato da Xcode.

Quindi puoi usare #if DEBUG "out of box".

A proposito, come utilizzare i blocchi di compilazione delle condizioni in generale è scritto nel libro Apple Swift Programming Language 4.1 (la sezione Dichiarazioni sul controllo del compilatore) e come scrivere i flag di compilazione e ciò che è la controparte delle macro C in Swift è scritto in un altro libro di Apple che utilizza Swift con Cocoa e Objective C (nella sezione Direttive del preprocessore)

Spero che in futuro Apple scriva i contenuti più dettagliati e gli indici per i loro libri.


17

XCODE 9 E SOPRA

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif

3
wow questa è la sigla più brutta che abbia mai visto: p
rmp251

7

Dopo aver impostato DEBUG=1le GCC_PREPROCESSOR_DEFINITIONSImpostazioni build, preferisco utilizzare una funzione per effettuare queste chiamate:

func executeInProduction(_ block: () -> Void)
{
    #if !DEBUG
        block()
    #endif
}

E poi racchiudi in questa funzione qualsiasi blocco che voglio omesso nelle build di debug:

executeInProduction {
    Fabric.with([Crashlytics.self]) // Compiler checks this line even in Debug
}

Il vantaggio rispetto a:

#if !DEBUG
    Fabric.with([Crashlytics.self]) // This is not checked, may not compile in non-Debug builds
#endif

È che il compilatore controlla la sintassi del mio codice, quindi sono sicuro che la sua sintassi sia corretta e build.



3
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

fonte


1
Questa non è una compilazione condizionale. Sebbene utile, è solo un vecchio condizionale runtime. L'OP chiede dopo il compiletime per scopi di metaprogrammazione
Shayne,

3
Basta aggiungere @inlinabledi fronte funce questo sarebbe il modo più elegante e idiomatico per Swift. Nelle build di rilascio il code()blocco verrà ottimizzato ed eliminato del tutto. Una funzione simile viene utilizzata nel framework NIO di Apple.
Mojuba,

1

Questo si basa sulla risposta di Jon Willis che si basa sull'affermazione , che viene eseguita solo nelle compilation di debug:

func Log(_ str: String) { 
    assert(DebugLog(str)) 
}
func DebugLog(_ str: String) -> Bool { 
    print(str) 
    return true
}

Il mio caso d'uso è per la registrazione delle dichiarazioni di stampa. Ecco un benchmark per la versione di rilascio su iPhone X:

let iterations = 100_000_000
let time1 = CFAbsoluteTimeGetCurrent()
for i in 0 ..< iterations {
    Log ("⧉ unarchiveArray:\(fileName) memoryTime:\(memoryTime) count:\(array.count)")
}
var time2 = CFAbsoluteTimeGetCurrent()
print ("Log: \(time2-time1)" )

stampe:

Log: 0.0

Sembra che Swift 4 elimini completamente la chiamata di funzione.


Elimina, poiché rimuove la chiamata nella sua interezza quando non è in debug - a causa della funzione vuota? Sarebbe perfetto.
Johan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.