#ifdef zamiennik w języku Swift

735

W C / C ++ / Objective C możesz zdefiniować makro za pomocą preprocesorów kompilatora. Co więcej, możesz włączyć / wyłączyć niektóre części kodu za pomocą preprocesorów kompilatora.

#ifdef DEBUG
    // Debug-only code
#endif

Czy istnieje podobne rozwiązanie w Swift?

Mxg
źródło
1
Jako pomysł, możesz umieścić to w nagłówkach mostkujących obj-c ..
Matej
42
Naprawdę powinieneś udzielić odpowiedzi, ponieważ masz kilka do wyboru, a to pytanie dało ci dużo głosów.
David H

Odpowiedzi:

1069

Tak, możesz to zrobić.

W Swift nadal możesz używać makr preprocesora „# if / # else / # endif” (choć bardziej ograniczone), zgodnie z dokumentami Apple . Oto przykład:

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

Teraz jednak musisz ustawić symbol „DEBUG” w innym miejscu. Ustaw go w sekcji „Swift Compiler - Custom Flags”, w wierszu „Other Swift Flags”. Dodajesz symbol DEBUG z -D DEBUGwpisem.

Jak zwykle, możesz ustawić inną wartość podczas debugowania lub wydania.

Przetestowałem to w prawdziwym kodzie i działa; wydaje się, że nie jest rozpoznawany na placu zabaw.

Możesz przeczytać mój oryginalny post tutaj .


WAŻNA UWAGA: -DDEBUG=1 nie działa. -D DEBUGDziała tylko . Wydaje się, że kompilator ignoruje flagę o określonej wartości.

Jean Le Moignan
źródło
41
To poprawna odpowiedź, chociaż należy zauważyć, że można sprawdzić tylko obecność flagi, ale nie określoną wartość.
Charles Harley,
19
Uwaga dodatkowa : Oprócz dodawania, -D DEBUGjak wspomniano powyżej, musisz także zdefiniować DEBUG=1w Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros
38
Nie mogłem tego uruchomić, dopóki nie zmieniłem formatowania -DDEBUGz tej odpowiedzi: stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros Nie ma potrzeby, aby dodać DEBUG=1do Preprocessor Macros, jeśli nie chcesz go używać w kodzie Objective-C.
derpoliuk
7
@Daniel Możesz używać standardowych operatorów boolowskich (np .: `#if!
DEBUG`
353

Jak stwierdzono w Apple Docs

Kompilator Swift nie zawiera preprocesora. Zamiast tego wykorzystuje atrybuty czasu kompilacji, konfiguracje kompilacji i funkcje językowe, aby osiągnąć tę samą funkcjonalność. Z tego powodu dyrektywy preprocesora nie są importowane do Swift.

Udało mi się osiągnąć to, co chciałem, używając niestandardowych konfiguracji kompilacji:

  1. Przejdź do swojego projektu / wybierz cel / Ustawienia budowania / wyszukaj Flagi niestandardowe
  2. Dla wybranego celu ustaw niestandardową flagę, używając prefiksu -D (bez białych spacji), zarówno dla debugowania, jak i zwolnienia
  3. Wykonaj powyższe kroki dla każdego swojego celu

Oto jak sprawdzasz cel:

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

wprowadź opis zdjęcia tutaj

Testowane przy użyciu Swift 2.2

Andrej
źródło
4
1. przy pracy z białą przestrzenią 2. czy powinien ustawić flagę tylko dla Debugowania?
około
3
@ c0ming zależy od twoich potrzeb, ale jeśli chcesz, aby coś się wydarzyło tylko w trybie debugowania, a nie w wydaniu, musisz usunąć -DDEBUG z wydania.
cdf1982,
1
Po ustawieniu niestandardowej flagi -DLOCALna mój #if LOCAl #else #endifwpada do #elsesekcji. Zduplikowałem oryginalny cel AppTargeti zmieniłem jego nazwę na AppTargetLocali ustawiłem niestandardową flagę.
Perwyl Liu,
3
@Andrej, czy wiesz, jak sprawić, by XCTest rozpoznał także niestandardowe flagi? Zdaję sobie sprawę, że wpada w #if LOCAL zamierzony rezultat, kiedy uruchamiam z symulatorem i wpada #else podczas testowania. Chcę, żeby do tego doszło #if LOCALrównież podczas testów.
Perwyl Liu,
3
To powinna być zaakceptowana odpowiedź. Aktualnie przyjęta odpowiedź jest nieprawidłowa dla Swift, ponieważ dotyczy tylko Celu C.
miken.mkndev
171

W wielu sytuacjach tak naprawdę nie potrzebujesz kompilacji warunkowej ; potrzebujesz tylko zachowania warunkowego , które możesz włączać i wyłączać. W tym celu możesz użyć zmiennej środowiskowej. Ma to tę ogromną zaletę, że tak naprawdę nie trzeba go ponownie kompilować.

Możesz ustawić zmienną środowiskową i łatwo ją włączyć lub wyłączyć w edytorze schematów:

wprowadź opis zdjęcia tutaj

Możesz pobrać zmienną środowiskową za pomocą NSProcessInfo:

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

Oto prawdziwy przykład. Moja aplikacja działa tylko na urządzeniu, ponieważ korzysta z biblioteki muzycznej, która nie istnieje w Symulatorze. Jak zatem robić zrzuty ekranu na symulatorze dla urządzeń, których nie posiadam? Bez tych zrzutów ekranu nie mogę przesłać do AppStore.

Potrzebuję fałszywych danych i innego sposobu ich przetwarzania . Mam dwie zmienne środowiskowe: jedna, która po włączeniu informuje aplikację, aby wygenerowała fałszywe dane z rzeczywistych danych podczas działania na moim urządzeniu; drugi, który po włączeniu korzysta z fałszywych danych (nie brakującej biblioteki muzycznej) podczas działania na symulatorze. Włączanie / wyłączanie każdego z tych trybów specjalnych jest łatwe dzięki polom wyboru zmiennych środowiskowych w edytorze schematów. Dodatkową zaletą jest to, że nie mogę przypadkowo użyć ich w mojej wersji App Store, ponieważ archiwizacja nie ma zmiennych środowiskowych.

matowy
źródło
Z jakiegoś powodu moja zmienna środowiskowa powróciła jako zero przy drugim uruchomieniu aplikacji
Eugene
60
Uwaga : zmienne środowiskowe są ustawione dla wszystkich konfiguracji kompilacji, nie można ich ustawić dla poszczególnych konfiguracji. Nie jest to zatem realne rozwiązanie, jeśli zachodzi potrzeba zmiany zachowania w zależności od tego, czy jest to wydanie, czy kompilacja debugowania.
Eric
5
@Eric uzgodnione, ale nie są ustawione dla wszystkich działań programu. Możesz więc zrobić jedną rzecz w przypadku kompilacji i uruchomić, a inną w archiwum, którym często jest prawdziwe rozróżnienie, które chcesz narysować. Lub możesz mieć wiele schematów, które są również powszechnym wzorem. Ponadto, jak powiedziałem w mojej odpowiedzi, włączanie i wyłączanie zmiennych środowiskowych w schemacie jest łatwe.
mat
10
Zmienne środowiskowe NIE działają w trybie archiwizacji. Są one stosowane tylko po uruchomieniu aplikacji z XCode. Jeśli spróbujesz uzyskać do nich dostęp na urządzeniu, aplikacja ulegnie awarii. Przekonałem się na własnej skórze.
iupchris10,
2
@ iupchris10 „Archiwizacja nie ma zmiennych środowiskowych” to ostatnie słowa mojej odpowiedzi powyżej. To, jak mówię w mojej odpowiedzi, jest dobre . Jest to punkt .
mat
159

Główną zmianą ifdefzastąpienia była Xcode 8. tj. Użycie Aktywnych warunków kompilacji .

Zobacz Budowanie i łączenie w Uwagach Xcode 8 .

Nowe ustawienia kompilacji

Nowe ustawienie: SWIFT_ACTIVE_COMPILATION_CONDITIONS

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

Wcześniej musieliśmy zadeklarować flagi warunkowej kompilacji w ramach opcji OTHER_SWIFT_FLAGS, pamiętając o dodaniu „-D” do ustawienia. Na przykład, aby warunkowo skompilować z wartością MYFLAG:

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

Wartość do dodania do ustawienia -DMYFLAG

Teraz musimy tylko przekazać wartość MYFLAG do nowego ustawienia. Czas przenieść wszystkie te warunkowe wartości kompilacji!

Proszę zapoznać się z poniższym linkiem, aby uzyskać więcej funkcji Swift Build Settings w Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

DShah
źródło
Czy istnieje możliwość wyłączenia zestawu aktywnych warunków kompilacji w czasie kompilacji? Muszę wyłączyć warunek DEBUGOWANIA podczas budowania konfiguracji debugowania do testowania.
Jonny
1
@Jonny Jedyny sposób, jaki znalazłem, to utworzenie konfiguracji 3. kompilacji dla projektu. Na stronie Projekt> karta Informacje> Konfiguracje naciśnij „+”, a następnie zduplikuj debugowanie. Następnie możesz dostosować warunki aktywnej kompilacji dla tej konfiguracji. Nie zapomnij edytować Cel> Schematy testowe, aby użyć nowej konfiguracji kompilacji!
Matthias,
1
To powinna być poprawna odpowiedź. To jedyna rzecz, która działała dla mnie na xCode 9 przy użyciu Swift 4.x!
shokaveli
1
BTW, w Xcode 9.3 Swift 4.1 DEBUG jest już dostępny w aktywnych warunkach kompilacji i nie musisz nic dodawać, aby sprawdzić konfigurację DEBUG. Po prostu #if DEBUG i #endif.
Denis Kutlubaev
Myślę, że jest to zarówno nie na temat, jak i zła rzecz do zrobienia. nie chcesz wyłączać aktywnych warunków kompilacji. potrzebujesz nowej i innej konfiguracji do testowania - na której NIE będzie tagu „Debug”. Dowiedz się o schematach.
Motti Shneor
93

Począwszy od wersji Swift 4.1, jeśli wszystko, czego potrzebujesz, to po prostu sprawdź, czy kod jest zbudowany z konfiguracją debugowania lub wydania, możesz użyć wbudowanych funkcji:

  • _isDebugAssertConfiguration()(prawda, gdy optymalizacja jest ustawiona na -Onone)
  • _isReleaseAssertConfiguration()(prawda, gdy optymalizacja jest ustawiona na -O) (niedostępne w Swift 3+)
  • _isFastAssertConfiguration()(prawda, gdy optymalizacja jest ustawiona na -Ounchecked)

na przykład

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

W porównaniu z makrami preprocesora,

  • ✓ Nie trzeba definiować niestandardowej -D DEBUGflagi, aby z niej korzystać
  • ~ To jest faktycznie zdefiniowane w kategoriach ustawień optymalizacji, a nie konfiguracji kompilacji Xcode
  • ✗ Nieudokumentowane, co oznacza, że ​​funkcję można usunąć w dowolnej aktualizacji (ale powinna być bezpieczna dla AppStore, ponieważ optymalizator zamieni je w stałe)

  • ✗ Używanie w, jeśli / else zawsze generuje ostrzeżenie „Nigdy nie zostanie wykonane”.

kennytm
źródło
1
Czy te wbudowane funkcje są oceniane podczas kompilacji lub w czasie wykonywania?
ma11hew28
@MattDiPasquale Czas optymalizacji. if _isDebugAssertConfiguration()zostanie oceniony jako if falsew trybie zwolnienia i if truejest trybem debugowania.
kennytm
2
Nie mogę jednak użyć tych funkcji, aby zrezygnować z niektórych zmiennych tylko do debugowania w wydaniu.
Franklin Yu,
3
Czy te funkcje są gdzieś udokumentowane?
Tom Harrington
7
Począwszy od Swift 3.0 i XCode 8, funkcje te są nieprawidłowe.
CodeBender
86

Xcode 8 i nowsze wersje

Użyj ustawienia Aktywna kompilacja w Ustawieniach kompilacji / Kompilatora Swift - Niestandardowe flagi .

  • To jest nowe ustawienie kompilacji do przekazywania flag kompilacji warunkowej do kompilatora Swift.
  • Proste ADD flagi takie jak ten: ALPHA, BETAetc.

Następnie sprawdź to z takimi warunkami kompilacji :

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

Wskazówka: możesz także użyć #if !ALPHAitp.

Jakub Truhlář
źródło
77

Nie ma preprocesora Swift. (Po pierwsze, dowolne podstawienie kodu narusza bezpieczeństwo typu i pamięci.)

Swift zawiera jednak opcje konfiguracji czasu kompilacji, więc można warunkowo dołączyć kod dla niektórych platform lub stylów kompilacji lub w odpowiedzi na flagi zdefiniowane za pomocą -Dargumentów kompilatora. Jednak w odróżnieniu od C sekcja warunkowo skompilowana musi być kompletna pod względem składniowym. Jest o tym mowa w Używanie Swift z kakao i Objective-C .

Na przykład:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
Rickster
źródło
34
„Po pierwsze, arbitralne zastępowanie kodu narusza bezpieczeństwo typu i pamięci”. Czy procesor wstępny nie działa przed kompilatorem (stąd nazwa)? Tak więc wszystkie te kontrole mogą nadal mieć miejsce.
Thilo,
10
@ Thilo Myślę, że to, co psuje, to wsparcie IDE
Aleksandr Dubinsky
1
Myślę, że @Rickster zmierza do tego, że makra C Preprocessor nie rozumieją typu, a ich obecność złamałaby wymagania Swift dotyczące typu. Powodem, dla którego makra działają w C, jest to, że C umożliwia niejawną konwersję typów, co oznacza, że ​​możesz umieścić swoje w INT_CONSTdowolnym miejscu, floatktóre zostanie zaakceptowane. Swift na to nie pozwoli. Ponadto, jeśli mógłbyś to zrobić var floatVal = INT_CONSTnieuchronnie, załamałby się gdzieś później, gdy kompilator oczekuje, Intale używasz go jako Float(typ floatValbyłby wywnioskowany jako Int). 10 rzutów później i po prostu czystsze usuwanie makr ...
Ephemera
Próbuję tego użyć, ale wydaje się, że to nie działa, nadal kompiluje kod Maca w kompilacjach iOS. Czy jest gdzieś jeszcze inny ekran konfiguracji, który należy dostosować?
Maury Markowitz
1
@ Thilo masz rację - procesor wstępny nie narusza żadnego typu ani bezpieczeństwa pamięci.
tcurdt
50

Moje dwa centy za Xcode 8:

a) Niestandardowa flaga korzystająca z -Dprefiksu działa dobrze, ale ...

b) Prostsze użycie:

W Xcode 8 dostępna jest nowa sekcja: „Aktywne warunki kompilacji”, już z dwoma wierszami, do debugowania i wydania.

Po prostu dodaj swoją definicję BEZ -D.

ingconti
źródło
Dzięki, że wspomniałeś, że istnieją dwa wiersze do debugowania i wydania
Yitzchak,
ktoś przetestował to w wydaniu?
Glenn,
To jest zaktualizowana odpowiedź dla szybkich użytkowników. tzn -D. bez .
Mani,
46

Stała isDebug oparta na aktywnych warunkach kompilacji

Innym, być może prostszym rozwiązaniem, które wciąż daje wynik logiczny, który można przekazać do funkcji bez pieprzania #ifwarunków warunkowych w całej bazie kodu, jest zdefiniowanie DEBUGjednego z celów kompilacji projektu Active Compilation Conditionsi uwzględnienie następujących (definiuję go jako stałą globalną):

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

Stała isDebug oparta na ustawieniach optymalizacji kompilatora

Ta koncepcja opiera się na odpowiedzi KennyTM

Główną zaletą w porównaniu z kennyTM jest to, że nie opiera się to na prywatnych ani nieudokumentowanych metodach.

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

W porównaniu z makrami preprocesora i odpowiedzią KennyTM ,

  • ✓ Nie trzeba definiować niestandardowej -D DEBUGflagi, aby z niej korzystać
  • ~ To jest faktycznie zdefiniowane w kategoriach ustawień optymalizacji, a nie konfiguracji kompilacji Xcode
  • Udokumentowane , co oznacza, że ​​funkcja będzie działać zgodnie z normalnymi wzorcami wydawania / wycofywania API.

  • ✓ Zastosowanie w, jeśli / else nie wygeneruje ostrzeżenia „Nigdy nie zostanie wykonane”.

Jon Willis
źródło
25

Moignans, odpowiedź tutaj działa dobrze. Oto kolejny spokój informacji na wypadek, gdyby to pomogło,

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

Możesz zanegować makra jak poniżej,

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif
Sazzad Hissain Khan
źródło
23

W projektach Swift utworzonych za pomocą Xcode w wersji 9.4.1, Swift 4.1

#if DEBUG
#endif

działa domyślnie, ponieważ w Makrach Preprocesora DEBUG = 1 został już ustawiony przez Xcode.

Możesz więc użyć #if DEBUG „po wyjęciu z pudełka”.

Nawiasem mówiąc, jak ogólnie korzystać z bloków kompilacji warunków, jest napisane w książce Apple'a Swift Programming Language 4.1 (sekcja Instrukcje sterujące kompilatora) i jak napisać flagi kompilacji i co jest odpowiednikiem makr C w Swift jest napisane w kolejna książka Apple'a Używanie Swift z kakao i celem C (w sekcji Dyrektywy preprocesora)

Mam nadzieję, że w przyszłości Apple napisze bardziej szczegółowe treści i indeksy swoich książek.

Vadim Motorine
źródło
17

XCODE 9 I POWYŻEJ

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
midhun p
źródło
3
wow, to jest najbrzydszy skrót, jaki kiedykolwiek widziałem: p
rmp251
7

Po ustawieniu DEBUG=1w GCC_PREPROCESSOR_DEFINITIONSUstawieniach kompilacji wolę używać funkcji do wykonywania tych wywołań:

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

A następnie dołącz do tej funkcji dowolny blok, który chcę pominąć w kompilacjach Debugowania:

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

Zaleta w porównaniu do:

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

Czy kompilator sprawdza składnię mojego kodu, więc jestem pewien, że jego składnia jest poprawna i buduje.

Rivera
źródło
2
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

Źródło

Adam Smaka
źródło
1
To nie jest kompilacja warunkowa. Choć jest użyteczny, jest to zwykły stary warunek działania. OP prosi po zakończeniu do metaprogramowania
Shayne
3
Wystarczy dodać @inlinableprzed, funca byłby to najbardziej elegancki i idiomatyczny sposób dla Swift. W kompilacjach wersji twój code()blok zostanie zoptymalizowany i całkowicie wyeliminowany. Podobna funkcja jest używana we własnym środowisku NIO firmy Apple.
mojuba
1

Opiera się to na odpowiedzi Jona Willisa, która opiera się na aser, która jest wykonywana tylko w kompilacjach Debug:

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

Mój przypadek użycia dotyczy rejestrowania drukowanych wyciągów. Oto punkt odniesienia dla wersji Release na 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)" )

drukuje:

Log: 0.0

Wygląda na to, że Swift 4 całkowicie eliminuje wywołanie funkcji.

Warren Stringer
źródło
Eliminuje, ponieważ usuwa całe wywołanie, gdy nie jest debugowane - z powodu pustej funkcji? To by było idealne.
Johan