# ifdef-Ersatz in der Swift-Sprache

735

In C / C ++ / Objective C können Sie ein Makro mithilfe von Compiler-Präprozessoren definieren. Darüber hinaus können Sie einige Codeteile mithilfe von Compiler-Präprozessoren ein- / ausschließen.

#ifdef DEBUG
    // Debug-only code
#endif

Gibt es eine ähnliche Lösung in Swift?

mxg
quelle
1
Als Idee könnten Sie dies in Ihre obj-c Überbrückungsheader einfügen.
Matej
42
Sie sollten wirklich eine Antwort vergeben, da Sie mehrere zur Auswahl haben, und diese Frage hat Ihnen viele Stimmen eingebracht.
David H

Antworten:

1069

Ja, du kannst es schaffen.

In Swift können Sie weiterhin die Präprozessor-Makros "# if / # else / # endif" (obwohl eingeschränkter) gemäß Apple-Dokumenten verwenden . Hier ist ein Beispiel:

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

Jetzt müssen Sie das Symbol "DEBUG" jedoch an einer anderen Stelle setzen. Stellen Sie es im Abschnitt "Swift Compiler - Benutzerdefinierte Flags" in der Zeile "Andere Swift Flags" ein. Sie fügen dem -D DEBUGEintrag das DEBUG-Symbol hinzu .

Wie üblich können Sie im Debug oder im Release einen anderen Wert festlegen.

Ich habe es in echtem Code getestet und es funktioniert; es scheint jedoch auf einem Spielplatz nicht erkannt zu werden.

Sie können meinen ursprünglichen Beitrag hier lesen .


WICHTIGER HINWEIS: -DDEBUG=1 funktioniert nicht. Funktioniert nur -D DEBUG. Der Compiler ignoriert anscheinend ein Flag mit einem bestimmten Wert.

Jean Le Moignan
quelle
41
Dies ist die richtige Antwort, obwohl zu beachten ist, dass Sie nur das Vorhandensein des Flags überprüfen können, jedoch keinen bestimmten Wert.
Charles Harley
19
Zusätzlicher Hinweis : Zusätzlich zum Hinzufügen -D DEBUGwie oben angegeben müssen Sie auch DEBUG=1in Apple LLVM 6.0 - Preprocessing-> definieren Preprocessor Macros.
Matthew Quiros
38
Ich konnte dies nicht zum Laufen bringen, bis ich die Formatierung -DDEBUGvon dieser Antwort in geändert habe : stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros Es gibt keine Notwendigkeit fügen DEBUG=1zu Preprocessor Macros, wenn Sie es im Code Objective-C nicht verwendet werden sollen.
derpoliuk
7
@ Daniel Sie können Standard-Boolesche Operatoren verwenden (zB: #if! DEBUG)
Jean Le Moignan
353

Wie in Apple Docs angegeben

Der Swift-Compiler enthält keinen Präprozessor. Stattdessen werden Attribute zur Kompilierungszeit, Build-Konfigurationen und Sprachfunktionen verwendet, um dieselbe Funktionalität zu erzielen. Aus diesem Grund werden Präprozessoranweisungen nicht in Swift importiert.

Ich habe es geschafft, mit benutzerdefinierten Build-Konfigurationen das zu erreichen, was ich wollte:

  1. Gehen Sie zu Ihrem Projekt / wählen Sie Ihr Ziel / Build-Einstellungen / suchen Sie nach benutzerdefinierten Flags
  2. Setzen Sie für Ihr ausgewähltes Ziel Ihr benutzerdefiniertes Flag mit dem Präfix -D (ohne Leerzeichen) für Debug und Release
  3. Führen Sie die obigen Schritte für jedes Ziel aus, das Sie haben

So überprüfen Sie das Ziel:

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

Geben Sie hier die Bildbeschreibung ein

Getestet mit Swift 2.2

Andrej
quelle
4
1.Wenn auch Leerzeichen funktionieren, 2.sollte das Flag nur für Debug gesetzt werden?
c0ming
3
@ c0ming es hängt von Ihren Anforderungen ab, aber wenn Sie möchten, dass etwas nur im Debug-Modus und nicht im Release passiert, müssen Sie -DDEBUG aus dem Release entfernen.
cdf1982
1
Nachdem ich das benutzerdefinierte Flag -DLOCALauf meinem gesetzt habe #if LOCAl #else #endif, fällt es in den #elseAbschnitt. Ich habe das ursprüngliche Ziel dupliziert AppTargetund es umbenannt AppTargetLocalund das benutzerdefinierte Flag gesetzt.
Perwyl Liu
3
@Andrej wissen Sie zufällig, wie XCTest auch die benutzerdefinierten Flags erkennt? Mir ist klar, dass es in #if LOCAL das beabsichtigte Ergebnis fällt , wenn ich mit dem Simulator laufe und #else beim Testen hineinfällt. Ich möchte, dass es #if LOCALauch beim Testen hineinfällt.
Perwyl Liu
3
Dies sollte die akzeptierte Antwort sein. Die aktuell akzeptierte Antwort ist für Swift falsch, da sie nur für Objective-C gilt.
miken.mkndev
171

In vielen Situationen benötigen Sie keine bedingte Kompilierung . Sie brauchen nur bedingtes Verhalten , das Sie ein- und ausschalten können. Dafür können Sie eine Umgebungsvariable verwenden. Dies hat den großen Vorteil, dass Sie nicht neu kompilieren müssen.

Sie können die Umgebungsvariable im Schema-Editor festlegen und einfach ein- oder ausschalten:

Geben Sie hier die Bildbeschreibung ein

Sie können die Umgebungsvariable mit NSProcessInfo abrufen:

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

Hier ist ein Beispiel aus dem wirklichen Leben. Meine App läuft nur auf dem Gerät, da sie die Musikbibliothek verwendet, die im Simulator nicht vorhanden ist. Wie mache ich dann Screenshots im Simulator für Geräte, die ich nicht besitze? Ohne diese Screenshots kann ich nicht an den AppStore senden.

Ich brauche gefälschte Daten und eine andere Art, sie zu verarbeiten . Ich habe zwei Umgebungsvariablen: eine, die die App beim Einschalten anweist, die gefälschten Daten aus den realen Daten zu generieren, während sie auf meinem Gerät ausgeführt wird; die andere, die beim Einschalten die gefälschten Daten (nicht die fehlende Musikbibliothek) verwendet, während sie auf dem Simulator ausgeführt wird. Das Ein- und Ausschalten jedes dieser speziellen Modi ist dank der Kontrollkästchen für Umgebungsvariablen im Schema-Editor einfach. Und der Bonus ist, dass ich sie nicht versehentlich in meinem App Store-Build verwenden kann, da die Archivierung keine Umgebungsvariablen enthält.

matt
quelle
Aus irgendeinem Grund wurde meine Umgebungsvariable beim zweiten App-Start als Null zurückgegeben
Eugene,
60
Achtung : Umgebungsvariablen werden für alle Build-Konfigurationen festgelegt, sie können nicht für einzelne Konfigurationen festgelegt werden. Dies ist also keine praktikable Lösung, wenn sich das Verhalten ändern muss, je nachdem, ob es sich um eine Version oder einen Debug-Build handelt.
Eric
5
@Eric Einverstanden, aber sie sind nicht für alle Schemaaktionen festgelegt. Sie können also eine Sache beim Erstellen und Ausführen und eine andere beim Archivieren ausführen. Dies ist häufig die Unterscheidung, die Sie im wirklichen Leben treffen möchten. Oder Sie könnten mehrere Schemata haben, die auch ein reales gemeinsames Muster sind. Wie ich in meiner Antwort sagte, ist es außerdem einfach, Umgebungsvariablen in einem Schema ein- und auszuschalten.
Matt
10
Umgebungsvariablen funktionieren NICHT im Archivmodus. Sie werden nur angewendet, wenn die App über XCode gestartet wird. Wenn Sie versuchen, auf einem Gerät darauf zuzugreifen, stürzt die App ab. Auf die harte Tour herausgefunden.
Iupchris10
2
@ iupchris10 "Archivierung hat keine Umgebungsvariablen" sind die letzten Worte meiner Antwort oben. Das ist gut , wie ich in meiner Antwort sage . Es ist der Punkt .
Matt
160

ifdefMit Xcode 8 wurde eine wesentliche Änderung des Ersatzes vorgenommen, dh die Verwendung der Bedingungen für die aktive Kompilierung .

Siehe Erstellen und Verknüpfen in Xcode 8 Versionshinweis .

Neue Build-Einstellungen

Neue Einstellung: SWIFT_ACTIVE_COMPILATION_CONDITIONS

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

Zuvor mussten wir Ihre bedingten Kompilierungsflags unter OTHER_SWIFT_FLAGS deklarieren und dabei daran denken, der Einstellung "-D" voranzustellen. Zum bedingten Kompilieren mit einem MYFLAG-Wert:

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

Der Wert, der der Einstellung hinzugefügt werden soll -DMYFLAG

Jetzt müssen wir nur noch den Wert MYFLAG an die neue Einstellung übergeben. Zeit, all diese bedingten Kompilierungswerte zu verschieben!

Weitere Funktionen für schnelle Build-Einstellungen in Xcode 8 finden Sie unter dem folgenden Link: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

DShah
quelle
Gibt es überhaupt eine Möglichkeit, eine bestimmte aktive Kompilierungsbedingungen zum Zeitpunkt der Erstellung zu deaktivieren? Ich muss die DEBUG-Bedingung deaktivieren, wenn ich die Debug-Konfiguration zum Testen erstelle.
Jonny
1
@Jonny Der einzige Weg, den ich gefunden habe, besteht darin, eine dritte Build-Konfiguration für das Projekt zu erstellen. Klicken Sie auf der Registerkarte Projekt> Info> Konfigurationen auf '+' und duplizieren Sie dann Debug. Sie können dann die aktiven Kompilierungsbedingungen für diese Konfiguration anpassen. Vergessen Sie nicht, Ihre Schemata Ziel> Test zu bearbeiten, um die neue Build-Konfiguration zu verwenden!
Matthias
1
Dies sollte die richtige Antwort sein. Es ist das einzige, was für mich unter xCode 9 mit Swift 4.x funktioniert hat!
Shokaveli
1
Übrigens, in Xcode 9.3 Swift 4.1 ist DEBUG bereits unter Active Compilation Conditions vorhanden, und Sie müssen nichts hinzufügen, um die DEBUG-Konfiguration zu überprüfen. Nur #if DEBUG und #endif.
Denis Kutlubaev
Ich denke, das ist sowohl nicht zum Thema als auch eine schlechte Sache. Sie möchten die aktiven Kompilierungsbedingungen nicht deaktivieren. Zum Testen benötigen Sie eine neue und andere Konfiguration, auf der sich NICHT das Tag "Debug" befindet. Erfahren Sie mehr über Schemata.
Motti Shneor
93

Ab Swift 4.1 können Sie die integrierten Funktionen verwenden, wenn Sie nur prüfen müssen, ob der Code mit Debug- oder Release-Konfiguration erstellt wurde:

  • _isDebugAssertConfiguration()(wahr, wenn die Optimierung auf eingestellt ist -Onone)
  • _isReleaseAssertConfiguration()(wahr, wenn die Optimierung auf eingestellt ist -O) (nicht verfügbar bei Swift 3+)
  • _isFastAssertConfiguration()(wahr, wenn die Optimierung auf eingestellt ist -Ounchecked)

z.B

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

Im Vergleich zu Präprozessor-Makros

  • ✓ Sie müssen kein benutzerdefiniertes -D DEBUGFlag definieren , um es zu verwenden
  • ~ Es wird tatsächlich anhand der Optimierungseinstellungen definiert, nicht anhand der Xcode-Build-Konfiguration
  • ✗ Undokumentiert, was bedeutet, dass die Funktion bei jedem Update entfernt werden kann (sie sollte jedoch AppStore-sicher sein, da das Optimierungsprogramm diese in Konstanten umwandelt).

  • ✗ Wenn Sie in if / else verwenden, wird immer die Warnung "Wird nie ausgeführt" generiert.

kennytm
quelle
1
Werden diese integrierten Funktionen zur Kompilierungs- oder Laufzeit ausgewertet?
ma11hew28
@MattDiPasquale Optimierungszeit. if _isDebugAssertConfiguration()wird if falseim Release-Modus ausgewertet und if trueist im Debug-Modus.
Kennytm
2
Ich kann diese Funktionen jedoch nicht verwenden, um eine Nur-Debug-Variable in der Version zu deaktivieren.
Franklin Yu
3
Sind diese Funktionen irgendwo dokumentiert?
Tom Harrington
7
Ab Swift 3.0 & XCode 8 sind diese Funktionen ungültig.
CodeBender
87

Xcode 8 und höher

Verwenden Sie die Einstellung Active Compilation Conditions unter Build Settings / Swift Compiler - Custom Flags .

  • Dies ist die neue Build-Einstellung zum Übergeben von bedingten Kompilierungsflags an den Swift-Compiler.
  • Einfache Add - Flags wie folgt aus : ALPHA, BETAusw.

Überprüfen Sie es dann mit folgenden Kompilierungsbedingungen :

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

Tipp: Sie können auch #if !ALPHAetc. verwenden

Jakub Truhlář
quelle
77

Es gibt keinen Swift-Präprozessor. (Zum einen beeinträchtigt die willkürliche Codesubstitution die Typ- und Speichersicherheit.)

Swift enthält jedoch Konfigurationsoptionen für die Build-Zeit, sodass Sie Code für bestimmte Plattformen oder Build-Stile oder als Reaktion auf Flags, die Sie mit -DCompiler-Argumenten definieren, bedingt einschließen können . Im Gegensatz zu C muss ein bedingt kompilierter Abschnitt Ihres Codes jedoch syntaktisch vollständig sein. Es gibt einen Abschnitt darüber in Verwenden von Swift With Cocoa und Objective-C .

Zum Beispiel:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
Rickster
quelle
34
"Zum einen beeinträchtigt die willkürliche Codesubstitution die Typ- und Speichersicherheit." Erledigt ein Vorprozessor seine Arbeit nicht vor dem Compiler (daher der Name)? All diese Überprüfungen könnten also noch stattfinden.
Thilo
10
@Thilo Ich denke, was es bricht, ist IDE-Unterstützung
Aleksandr Dubinsky
1
Ich denke, was @rickster vorhat, ist, dass C-Präprozessor-Makros kein Verständnis für den Typ haben und ihre Anwesenheit die Typanforderungen von Swift verletzen würde. Der Grund, warum Makros in C funktionieren, liegt darin, dass C eine implizite Typkonvertierung ermöglicht. Dies bedeutet, dass Sie Ihre Makros INT_CONSTüberall dort platzieren können float, wo sie akzeptiert werden. Swift würde das nicht zulassen. Wenn Sie dies var floatVal = INT_CONSTzwangsläufig tun könnten , würde es irgendwann später zusammenbrechen, wenn der Compiler eine erwartet Int, Sie sie jedoch als Float(Typ von wird floatValals abgeleitet Int) verwenden. 10 Würfe später und es ist nur sauberer, um Makros zu entfernen ...
Ephemera
Ich versuche dies zu verwenden, aber es scheint nicht zu funktionieren, es kompiliert immer noch den Mac-Code auf iOS-Builds. Gibt es irgendwo einen anderen Setup-Bildschirm, der angepasst werden muss?
Maury Markowitz
1
@Thilo Sie sind richtig - ein Vorprozessor bricht keine Art oder Speichersicherheit.
tcurdt
50

Meine zwei Cent für Xcode 8:

a) Ein benutzerdefiniertes Flag mit dem -DPräfix funktioniert einwandfrei, aber ...

b) Einfachere Verwendung:

In Xcode 8 gibt es einen neuen Abschnitt: "Active Compilation Conditions", bereits mit zwei Zeilen, zum Debuggen und Freigeben.

Fügen Sie einfach Ihre Definition OHNE hinzu -D.

ingconti
quelle
Vielen Dank für die Erwähnung, dass es zwei Zeilen für Debug und Release gibt
Yitzchak
hat jemand dies in der Veröffentlichung getestet?
Glenn
Dies ist die aktualisierte Antwort für schnelle Benutzer. dh ohne -D.
Mani
46

isDebug-Konstante basierend auf aktiven Kompilierungsbedingungen

Eine andere, vielleicht einfachere Lösung, die immer noch zu einem Booleschen Wert führt, den Sie an Funktionen übergeben können, ohne die #ifBedingungen in Ihrer Codebasis zu verbessern , besteht darin, DEBUGals eines Ihrer Projekterstellungsziele zu definieren Active Compilation Conditionsund Folgendes einzuschließen (ich definiere es als globale Konstante):

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

isDebug-Konstante basierend auf den Einstellungen für die Compileroptimierung

Dieses Konzept baut auf der Antwort von kennytm auf

Der Hauptvorteil beim Vergleich mit Kennytms besteht darin, dass dies nicht auf privaten oder nicht dokumentierten Methoden beruht.

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
}()

Im Vergleich mit Präprozessormakros und Antwort des kennytm ,

  • ✓ Sie müssen kein benutzerdefiniertes -D DEBUGFlag definieren , um es zu verwenden
  • ~ Es wird tatsächlich anhand der Optimierungseinstellungen definiert, nicht anhand der Xcode-Build-Konfiguration
  • Dokumentiert , was bedeutet, dass die Funktion normalen API-Freigabe- / Verfallsmustern folgt.

  • ✓ Wenn Sie in if / else verwenden, wird keine Warnung "Wird niemals ausgeführt" generiert.

Jon Willis
quelle
25

Moignans Antwort hier funktioniert gut. Hier ist eine weitere Information, falls es hilft,

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

Sie können die Makros wie folgt negieren.

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif
Sazzad Hissain Khan
quelle
23

In Swift-Projekten, die mit Xcode Version 9.4.1 erstellt wurden, ist Swift 4.1

#if DEBUG
#endif

funktioniert standardmäßig, da in den Präprozessor-Makros DEBUG = 1 bereits von Xcode festgelegt wurde.

Sie können also #if DEBUG "out of box" verwenden.

Übrigens ist in Apples Buch The Swift Programming Language 4.1 (Abschnitt Compiler Control Statements) beschrieben, wie die Bedingungskompilierungsblöcke im Allgemeinen verwendet werden und wie die Kompilierungsflags geschrieben werden und was das Gegenstück zu den C-Makros in Swift ist ein anderes Apple-Buch Verwenden von Swift mit Kakao und Ziel C (im Abschnitt Präprozessor-Richtlinien)

Wir hoffen, dass Apple in Zukunft die detaillierteren Inhalte und die Indizes für ihre Bücher schreiben wird.

Vadim Motorine
quelle
17

XCODE 9 UND OBEN

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
midhun p
quelle
3
wow das ist die hässlichste Abkürzung, die ich je gesehen habe: p
rmp251
7

Nach dem Festlegen DEBUG=1Ihrer GCC_PREPROCESSOR_DEFINITIONSBuild-Einstellungen bevorzuge ich die Verwendung einer Funktion, um diese Aufrufe zu tätigen:

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

Und dann fügen Sie in diese Funktion einfach jeden Block ein, den ich in Debug-Builds weglassen möchte:

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

Der Vorteil gegenüber:

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

Ist das, dass der Compiler die Syntax meines Codes überprüft, so bin ich sicher, dass seine Syntax korrekt ist und erstellt.

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

Quelle

Adam Smaka
quelle
1
Dies ist keine bedingte Kompilierung. Es ist zwar nützlich, aber nur eine einfache alte Laufzeitbedingung. Das OP fragt nach der Kompilierungszeit nach Metaprogrammierungszwecken
Shayne
3
Fügen Sie einfach @inlinablevor funcund dies wäre der eleganteste und idiomatischste Weg für Swift. In Release-Builds wird Ihr code()Block optimiert und vollständig entfernt. Eine ähnliche Funktion wird in Apples eigenem NIO-Framework verwendet.
Mojuba
1

Dies baut auf Jon Willis ' Antwort auf, die auf Assert beruht und nur in Debug-Kompilierungen ausgeführt wird:

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

Mein Anwendungsfall ist das Protokollieren von Druckanweisungen. Hier ist ein Benchmark für die Release-Version auf dem 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)" )

Drucke:

Log: 0.0

Es sieht so aus, als würde Swift 4 den Funktionsaufruf vollständig eliminieren.

Warren Stringer
quelle
Beseitigt, wie in entfernt den Aufruf in seiner Gesamtheit, wenn nicht im Debug - weil die Funktion leer ist? Das wäre perfekt.
Johan