#ifdef remplacement dans la langue Swift

734

En C / C ++ / Objective C, vous pouvez définir une macro à l'aide de préprocesseurs de compilateur. De plus, vous pouvez inclure / exclure certaines parties de code à l'aide de préprocesseurs de compilateur.

#ifdef DEBUG
    // Debug-only code
#endif

Existe-t-il une solution similaire dans Swift?

mxg
la source
1
À titre d'idée, vous pourriez mettre cela dans vos en-têtes de pontage obj-c ..
Matej
42
Vous devriez vraiment attribuer une réponse car vous en avez plusieurs parmi lesquelles choisir, et cette question vous a valu beaucoup de votes positifs.
David H

Réponses:

1069

Oui, vous pouvez le faire.

Dans Swift, vous pouvez toujours utiliser les macros de préprocesseur "# if / # else / # endif" (bien que plus contraintes), selon les documents Apple . Voici un exemple:

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

Maintenant, vous devez cependant placer le symbole "DEBUG" ailleurs. Réglez-le dans la section "Swift Compiler - Custom Flags", ligne "Other Swift Flags". Vous ajoutez le symbole DEBUG avec l' -D DEBUGentrée.

Comme d'habitude, vous pouvez définir une valeur différente dans Debug ou dans Release.

Je l'ai testé en vrai code et cela fonctionne; il ne semble cependant pas être reconnu dans une cour de récréation.

Vous pouvez lire mon post original ici .


REMARQUE IMPORTANTE: -DDEBUG=1 ne fonctionne pas. Fonctionne uniquement -D DEBUG. Le compilateur semble ignorer un indicateur avec une valeur spécifique.

Jean Le Moignan
la source
41
Il s'agit de la bonne réponse, mais il convient de noter que vous ne pouvez vérifier que la présence du drapeau, mais pas une valeur spécifique.
Charles Harley
19
Remarque supplémentaire : En plus d'ajouter -D DEBUGcomme indiqué ci-dessus, vous devez également définir DEBUG=1dans Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros
38
Je n'ai pas pu faire fonctionner cela jusqu'à ce que je modifie la mise en forme -DDEBUGde cette réponse: stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros Il n'est pas nécessaire d'ajouter DEBUG=1à Preprocessor Macros, si vous ne voulez pas l'utiliser dans le code Objective-C.
derpoliuk
7
@Daniel Vous pouvez utiliser des opérateurs booléens standard (ex: `#if! DEBUG`)
Jean Le Moignan
353

Comme indiqué dans Apple Docs

Le compilateur Swift ne comprend pas de préprocesseur. Au lieu de cela, il tire parti des attributs au moment de la compilation, des configurations de génération et des fonctionnalités de langage pour accomplir la même fonctionnalité. Pour cette raison, les directives de préprocesseur ne sont pas importées dans Swift.

J'ai réussi à réaliser ce que je voulais en utilisant des configurations de construction personnalisées:

  1. Accédez à votre projet / sélectionnez votre cible / Paramètres de construction / recherchez des indicateurs personnalisés
  2. Pour la cible choisie, définissez votre indicateur personnalisé à l'aide du préfixe -D (sans espaces blancs), pour le débogage et la version
  3. Suivez les étapes ci-dessus pour chaque cible que vous avez

Voici comment vous vérifiez la cible:

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

entrez la description de l'image ici

Testé avec Swift 2.2

Andrej
la source
4
1. avec le travail en espace blanc également, 2.doit définir le drapeau uniquement pour le débogage?
c0ming
3
@ c0ming, cela dépend de vos besoins, mais si vous voulez que quelque chose se passe uniquement en mode débogage, et non en version, vous devez supprimer -DDEBUG de la version.
cdf1982
1
Après avoir défini le drapeau personnalisé -DLOCAL, sur mon #if LOCAl #else #endif, il tombe dans la #elsesection. J'ai dupliqué la cible d'origine AppTargetet je l'ai renommée pour AppTargetLocaldéfinir son indicateur personnalisé.
Perwyl Liu
3
@Andrej savez-vous comment faire en sorte que XCTest reconnaisse également les drapeaux personnalisés? Je me rends compte qu'il tombe dans #if LOCAL le résultat souhaité lorsque je cours avec le simulateur et tombe #else pendant les tests. Je veux que ça tombe #if LOCALaussi pendant les tests.
Perwyl Liu
3
Ce devrait être la réponse acceptée. La réponse actuellement acceptée est incorrecte pour Swift car elle ne s'applique qu'à Objective-C.
miken.mkndev
171

Dans de nombreuses situations, vous n'avez pas vraiment besoin d'une compilation conditionnelle ; vous avez juste besoin d'un comportement conditionnel que vous pouvez activer et désactiver. Pour cela, vous pouvez utiliser une variable d'environnement. Cela a l'énorme avantage que vous n'avez pas à recompiler.

Vous pouvez définir la variable d'environnement et l'activer ou la désactiver facilement dans l'éditeur de schéma:

entrez la description de l'image ici

Vous pouvez récupérer la variable d'environnement avec NSProcessInfo:

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

Voici un exemple concret. Mon application fonctionne uniquement sur l'appareil, car elle utilise la bibliothèque musicale, qui n'existe pas sur le simulateur. Comment, alors, prendre des captures d'écran sur le simulateur pour des appareils que je ne possède pas? Sans ces captures d'écran, je ne peux pas me soumettre à l'AppStore.

J'ai besoin de fausses données et d'une manière différente de les traiter . J'ai deux variables d'environnement: une qui, lorsqu'elle est activée, indique à l'application de générer les fausses données à partir des données réelles lors de l'exécution sur mon appareil; l'autre qui, lorsqu'il est allumé, utilise les fausses données (pas la bibliothèque musicale manquante) lors de l'exécution sur le simulateur. Activer / désactiver chacun de ces modes spéciaux est facile grâce aux cases à cocher des variables d'environnement dans l'éditeur de schéma. Et le bonus est que je ne peux pas les utiliser accidentellement dans ma build App Store, car l'archivage n'a pas de variables d'environnement.

mat
la source
Pour une raison quelconque, ma variable d'environnement est revenue à zéro lors du deuxième lancement de l'application
Eugene
60
Attention : les variables d'environnement sont définies pour toutes les configurations de build, elles ne peuvent pas l'être pour les configurations individuelles. Ce n'est donc pas une solution viable si vous avez besoin que le comportement change selon qu'il s'agit d'une version ou d'une version de débogage.
Eric
5
@Eric Accepté, mais ils ne sont pas définis pour toutes les actions du schéma. Vous pouvez donc faire une chose lors de la construction et de l'exécution et une autre chose sur l'archivage, qui est souvent la distinction réelle que vous souhaitez faire. Ou vous pouvez avoir plusieurs schémas, qui constituent également un modèle commun dans la vie réelle. De plus, comme je l'ai dit dans ma réponse, activer et désactiver les variables d'environnement dans un schéma est facile.
mat
10
Les variables d'environnement ne fonctionnent PAS en mode archive. Ils ne sont appliqués que lorsque l'application est lancée à partir de XCode. Si vous essayez d'y accéder sur un appareil, l'application se bloque. Découvert à la dure.
iupchris10
2
@ iupchris10 "L'archivage n'a pas de variables d'environnement" sont les derniers mots de ma réponse, ci-dessus. Comme je l'ai dit dans ma réponse, c'est bien . C'est le point .
mat
161

Un changement majeur de ifdefremplacement est venu avec Xcode 8. c'est-à-dire l'utilisation des conditions de compilation active .

Reportez-vous à la section Création et liaison dans la note de publication de Xcode 8 .

Nouveaux paramètres de construction

Nouveau paramètre: SWIFT_ACTIVE_COMPILATION_CONDITIONS

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

Auparavant, nous devions déclarer vos indicateurs de compilation conditionnelle sous OTHER_SWIFT_FLAGS, en pensant à ajouter «-D» au paramètre. Par exemple, pour compiler conditionnellement avec une valeur MYFLAG:

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

La valeur à ajouter au paramètre -DMYFLAG

Il ne nous reste plus qu'à passer la valeur MYFLAG au nouveau paramètre. Il est temps de déplacer toutes ces valeurs de compilation conditionnelle!

Veuillez vous référer au lien ci-dessous pour plus de fonctionnalités de paramètres de construction rapide dans Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

DShah
la source
Est-il possible de désactiver un ensemble de conditions de compilation actives au moment de la génération? J'ai besoin de désactiver la condition DEBUG lors de la construction de la configuration de débogage pour les tests.
Jonny
1
@Jonny La seule façon que j'ai trouvée est de créer une 3e configuration de construction pour le projet. Dans l'onglet Projet> Infos> Configurations, appuyez sur '+', puis dupliquez le débogage. Vous pouvez ensuite personnaliser les conditions de compilation actives pour cette configuration. N'oubliez pas de modifier votre cible> schémas de test pour utiliser la nouvelle configuration de construction!
matthias
1
Cela devrait être la bonne réponse ... c'est la seule chose qui a fonctionné pour moi sur xCode 9 avec Swift 4.x!
shokaveli
1
BTW, dans Xcode 9.3 Swift 4.1 DEBUG est déjà là dans les conditions de compilation active et vous n'avez rien à ajouter pour vérifier la configuration de DEBUG. Juste #if DEBUG et #endif.
Denis Kutlubaev
Je pense que c'est à la fois hors sujet et une mauvaise chose à faire. vous ne souhaitez pas désactiver les conditions de compilation actives. vous avez besoin d'une configuration nouvelle et différente pour les tests - qui n'aura PAS la balise "Debug" dessus. En savoir plus sur les régimes.
Motti Shneor
93

Depuis Swift 4.1, si tout ce dont vous avez besoin est simplement de vérifier si le code est construit avec une configuration de débogage ou de publication, vous pouvez utiliser les fonctions intégrées:

  • _isDebugAssertConfiguration()(vrai lorsque l'optimisation est définie sur -Onone)
  • _isReleaseAssertConfiguration()(vrai lorsque l'optimisation est définie sur -O) (non disponible sur Swift 3+)
  • _isFastAssertConfiguration()(vrai lorsque l'optimisation est définie sur -Ounchecked)

par exemple

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

Par rapport aux macros de préprocesseur,

  • ✓ Vous n'avez pas besoin de définir un -D DEBUGindicateur personnalisé pour l'utiliser
  • ~ Il est en fait défini en termes de paramètres d'optimisation, pas de configuration de construction Xcode
  • ✗ Non documenté, ce qui signifie que la fonction peut être supprimée dans n'importe quelle mise à jour (mais elle devrait être sécurisée par AppStore car l'optimiseur les transformera en constantes)

  • ✗ L'utilisation de if / else générera toujours un avertissement "ne sera jamais exécuté".

kennytm
la source
1
Ces fonctions intégrées sont-elles évaluées lors de la compilation ou de l'exécution?
ma11hew28
@MattDiPasquale Temps d'optimisation. if _isDebugAssertConfiguration()sera évalué if falseen mode release et if trueest en mode debug.
kennytm
2
Cependant, je ne peux pas utiliser ces fonctions pour désactiver certaines variables de débogage uniquement dans la version.
Franklin Yu
3
Ces fonctions sont-elles documentées quelque part?
Tom Harrington
7
Depuis Swift 3.0 et XCode 8, ces fonctions ne sont pas valides.
CodeBender
87

Xcode 8 et supérieur

Utiliser des conditions de compilation actives paramètre dans Paramètres de compilation / Compilateur Swift - Indicateurs personnalisés .

  • Il s'agit du nouveau paramètre de génération permettant de passer des indicateurs de compilation conditionnelle au compilateur Swift.
  • Ajoutez simplement des drapeaux comme celui-ci: ALPHA , BETAetc.

Ensuite, vérifiez-le avec des conditions de compilation comme celle-ci:

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

Astuce: vous pouvez également utiliser #if !ALPHAetc.

Jakub Truhlář
la source
77

Il n'y a pas de préprocesseur Swift. (D'une part, la substitution de code arbitraire rompt la sécurité de type et de mémoire.)

Swift inclut des options de configuration au moment de la construction, donc vous pouvez inclure conditionnellement du code pour certaines plates-formes ou styles de construction ou en réponse aux indicateurs que vous définissez avec -Dles arguments du compilateur. Contrairement à C, cependant, une section conditionnellement compilée de votre code doit être syntaxiquement complète. Il y a une section à ce sujet dans Utilisation de Swift avec Cocoa et Objective-C .

Par exemple:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
rickster
la source
34
"D'une part, la substitution de code arbitraire rompt la sécurité de type et de mémoire." Un pré-processeur ne fait-il pas son travail avant le compilateur (d'où le nom)? Donc, tous ces contrôles pourraient encore avoir lieu.
Thilo
10
@Thilo Je pense que ce qu'il casse est le support IDE
Aleksandr Dubinsky
1
Je pense que @rickster veut dire que les macros du préprocesseur C ne comprennent pas le type et que leur présence briserait les exigences de type de Swift. La raison pour laquelle les macros fonctionnent en C est que C permet la conversion de type implicite, ce qui signifie que vous pouvez placer votre INT_CONSTn'importe où un floatserait accepté. Swift ne le permettrait pas. De plus, si vous pouviez le faire var floatVal = INT_CONSTinévitablement, cela tomberait en panne quelque part plus tard lorsque le compilateur attend un Intmais vous l'utilisez comme Float(le type de floatValserait inféré comme Int). 10 lancers plus tard et son nettoyant juste pour enlever les macros ...
Ephemera
J'essaie d'utiliser cela, mais cela ne semble pas fonctionner, il compile toujours le code Mac sur les versions iOS. Y a-t-il un autre écran de configuration quelque part qui doit être modifié?
Maury Markowitz
1
@Thilo vous avez raison - un pré-processeur ne casse aucun type ni aucune sécurité de la mémoire.
tcurdt
50

Mes deux cents pour Xcode 8:

a) Un indicateur personnalisé utilisant le -Dpréfixe fonctionne bien, mais ...

b) Utilisation plus simple:

Dans Xcode 8, il y a une nouvelle section: "Conditions de compilation actives", déjà avec deux lignes, pour le débogage et la publication.

Ajoutez simplement votre définition SANS -D.

ingconti
la source
Merci d'avoir mentionné qu'il y a DEUX RANGS POUR LE DÉBOGAGE ET LA LIBÉRATION
Yitzchak
quelqu'un a testé cela dans la version?
Glenn
Ceci est la réponse mise à jour pour les utilisateurs rapides. c'est à dire sans -D.
Mani
46

Constante isDebug basée sur les conditions de compilation actives

Une autre solution, peut-être plus simple, qui se traduit toujours par un booléen que vous pouvez passer à des fonctions sans peppering des #ifconditions dans votre base de code consiste à définir DEBUGcomme l'une des cibles de génération de votre projet Active Compilation Conditionset à inclure ce qui suit (je le définis comme une constante globale):

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

Constante isDebug basée sur les paramètres d'optimisation du compilateur

Ce concept s'appuie sur la réponse de kennytm

Le principal avantage lors de la comparaison avec kennytm est qu'il ne repose pas sur des méthodes privées ou non documentées.

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

Par rapport aux macros de préprocesseur et à la réponse de kennytm ,

  • ✓ Vous n'avez pas besoin de définir une coutume -D DEBUGindicateur pour l'utiliser
  • ~ Il est en fait défini en termes de paramètres d'optimisation, pas de configuration de construction Xcode
  • Documenté , ce qui signifie que la fonction suivra les modèles de libération / dépréciation d'API normaux.

  • ✓ L'utilisation de if / else ne générera pas d' avertissement "ne sera jamais exécuté".

Jon Willis
la source
25

La réponse de Moignans ici fonctionne bien. Voici une autre paix d'informations au cas où cela aiderait,

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

Vous pouvez annuler les macros comme ci-dessous,

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

Dans les projets Swift créés avec Xcode version 9.4.1, Swift 4.1

#if DEBUG
#endif

fonctionne par défaut car dans les macros du préprocesseur, DEBUG = 1 a déjà été défini par Xcode.

Vous pouvez donc utiliser #if DEBUG "out of box".

Soit dit en passant, comment utiliser les blocs de compilation de conditions en général est écrit dans le livre d'Apple The Swift Programming Language 4.1 (la section Instructions de contrôle du compilateur) et comment écrire les indicateurs de compilation et ce qui est l'équivalent des macros C dans Swift est écrit en un autre livre d'Apple Using Swift with Cocoa and Objective C (dans la section Directives du préprocesseur)

J'espère qu'à l'avenir, Apple rédigera le contenu plus détaillé et les index de leurs livres.

Vadim Motorine
la source
17

XCODE 9 ET PLUS

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
midhun p
la source
4
wow c'est l'abréviation la plus laide que j'aie jamais vue: p
rmp251
7

Après avoir défini DEBUG=1vos GCC_PREPROCESSOR_DEFINITIONSparamètres de construction, je préfère utiliser une fonction pour effectuer ces appels:

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

Et ensuite, insérez dans cette fonction tout bloc que je souhaite omettre dans les versions de débogage:

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

L'avantage par rapport à:

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

Est-ce que le compilateur vérifie la syntaxe de mon code, donc je suis sûr que sa syntaxe est correcte et se construit.

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

La source

Adam Smaka
la source
1
Ce n'est pas une compilation conditionnelle. Bien qu'utile, c'est juste un ancien conditionnel ordinaire. Le PO demande après le temps de compilation à des fins de métaprogrammation
Shayne
3
Ajoutez juste @inlinabledevant funcet ce serait la manière la plus élégante et idiomatique pour Swift. Dans les versions, votre code()bloc sera optimisé et complètement éliminé. Une fonction similaire est utilisée dans le propre framework NIO d'Apple.
mojuba
1

Cela s'appuie sur la réponse de Jon Willis qui s'appuie sur assert, qui n'est exécuté que dans les compilations de débogage:

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

Mon cas d'utilisation est pour la journalisation des relevés d'impression. Voici une référence pour la version Release sur 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)" )

impressions:

Log: 0.0

On dirait que Swift 4 élimine complètement l'appel de fonction.

Warren Stringer
la source
Élimine, car dans supprime l'appel dans son intégralité lorsqu'il n'est pas en débogage - en raison de la fonction étant vide? Cela serait parfait.
Johan