#ifdef reemplazo en el lenguaje Swift

734

En C / C ++ / Objective C puede definir una macro utilizando preprocesadores de compilación. Además, puede incluir / excluir algunas partes del código utilizando preprocesadores del compilador.

#ifdef DEBUG
    // Debug-only code
#endif

¿Hay una solución similar en Swift?

mxg
fuente
1
Como idea, podría poner esto en sus encabezados de puente obj-c ..
Matej
42
Realmente debería otorgar una respuesta, ya que tiene varias para elegir, y esta pregunta le ha dado muchos votos positivos.
David H

Respuestas:

1069

Sí, tú puedes hacerlo.

En Swift todavía puede usar las macros de preprocesador "# if / # else / # endif" (aunque más restringido), según los documentos de Apple . Aquí hay un ejemplo:

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

Ahora, sin embargo, debe establecer el símbolo "DEPURAR" en otro lugar. Configúrelo en la sección "Compilador Swift - Banderas personalizadas", línea "Otras banderas Swift". Agrega el símbolo DEPURAR con la -D DEBUGentrada.

Como de costumbre, puede establecer un valor diferente cuando está en Debug o cuando está en Release.

Lo probé en código real y funciona; sin embargo, no parece ser reconocido en un patio de recreo.

Puedes leer mi publicación original aquí .


NOTA IMPORTANTE: -DDEBUG=1 no funciona. Solo -D DEBUGfunciona. Parece que el compilador está ignorando una bandera con un valor específico.

Jean Le Moignan
fuente
41
Esta es la respuesta correcta, aunque debe tenerse en cuenta que solo puede verificar la presencia de la bandera pero no un valor específico.
Charles Harley
19
Nota adicional : además de agregar -D DEBUGcomo se indicó anteriormente, también debe definir DEBUG=1en Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros
38
No pude hacer que esto funcionara hasta que cambié el formato -DDEBUGde esta respuesta: stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros No hay necesidad de añadir DEBUG=1a Preprocessor Macros, si no se desea utilizar en el código Objective-C.
derpoliuk
77
@Daniel Puede usar operadores booleanos estándar (ej: `#if! DEBUG`)
Jean Le Moignan
353

Como se indica en Apple Docs

El compilador Swift no incluye un preprocesador. En cambio, aprovecha los atributos en tiempo de compilación, las configuraciones de compilación y las características del lenguaje para lograr la misma funcionalidad. Por este motivo, las directivas de preprocesador no se importan en Swift.

Me las arreglé para lograr lo que quería usando configuraciones de compilación personalizadas:

  1. Vaya a su proyecto / seleccione su objetivo / Configuración de compilación / busque Banderas personalizadas
  2. Para el objetivo elegido, configure su marca personalizada con el prefijo -D (sin espacios en blanco), tanto para depurar como para liberar
  3. Haz los pasos anteriores para cada objetivo que tengas

Así es como verifica el objetivo:

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

ingrese la descripción de la imagen aquí

Probado con Swift 2.2

Andrej
fuente
44
1. con espacio en blanco también, 2. ¿debería establecer la bandera solo para depuración?
Llegando el
3
@ c0ming depende de sus necesidades, pero si desea que algo suceda solo en modo de depuración, y no en la versión, debe eliminar -DDEBUG de la versión.
cdf1982
1
Después de configurar la bandera personalizada -DLOCAL, en mi #if LOCAl #else #endif, cae en la #elsesección. Dupliqué el objetivo original AppTargety le cambié el nombre a AppTargetLocaly configuré su bandera personalizada.
Perwyl Liu
3
@Andrej, ¿sabes cómo hacer que XCTest reconozca también las banderas personalizadas? Me doy cuenta de que cae en #if LOCAL el resultado deseado cuando corro con el simulador y cae #else durante las pruebas. Quiero que caiga #if LOCALtambién durante las pruebas.
Perwyl Liu
3
Esta debería ser la respuesta aceptada. La respuesta aceptada actual es incorrecta para Swift, ya que solo se aplica a Objective-C.
miken.mkndev
171

En muchas situaciones, realmente no necesita compilación condicional ; solo necesita un comportamiento condicional que pueda activar y desactivar. Para eso, puede usar una variable de entorno. Esto tiene la gran ventaja de que en realidad no tiene que volver a compilar.

Puede configurar la variable de entorno y activarla o desactivarla fácilmente en el editor de esquemas:

ingrese la descripción de la imagen aquí

Puede recuperar la variable de entorno con NSProcessInfo:

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

Aquí hay un ejemplo de la vida real. Mi aplicación solo se ejecuta en el dispositivo, porque usa la biblioteca de música, que no existe en el simulador. ¿Cómo, entonces, tomar capturas de pantalla en el simulador para dispositivos que no tengo? Sin esas capturas de pantalla, no puedo enviarme a la AppStore.

Necesito datos falsos y una forma diferente de procesarlos . Tengo dos variables de entorno: una que, cuando está encendida, le dice a la aplicación que genere datos falsos a partir de datos reales mientras se ejecuta en mi dispositivo; el otro que, cuando se enciende, usa los datos falsos (no la biblioteca de música que falta) mientras se ejecuta en el simulador. Activar / desactivar cada uno de esos modos especiales es fácil gracias a las casillas de verificación de las variables de entorno en el editor de esquemas. Y la ventaja es que no puedo usarlos accidentalmente en la compilación de mi tienda de aplicaciones, porque el archivo no tiene variables de entorno.

mate
fuente
Por alguna razón, mi variable de entorno regresó como nula en el segundo lanzamiento de la aplicación
Eugene el
6060
Cuidado : las variables de entorno están configuradas para todas las configuraciones de compilación, no pueden configurarse para configuraciones individuales Por lo tanto, esta no es una solución viable si necesita cambiar el comportamiento dependiendo de si se trata de una versión o una compilación de depuración.
Eric
55
@Eric De acuerdo, pero no están configurados para todas las acciones del esquema. Por lo tanto, podría hacer una cosa en compilar y ejecutar y otra diferente en el archivo, que a menudo es la distinción de la vida real que desea dibujar. O podría tener múltiples esquemas, que también son un patrón común de la vida real. Además, como dije en mi respuesta, activar y desactivar variables de entorno en un esquema es fácil.
mate
10
Las variables de entorno NO funcionan en modo de archivo. Solo se aplican cuando la aplicación se inicia desde XCode. Si intenta acceder a estos en un dispositivo, la aplicación se bloqueará. Descubierto por el camino difícil.
iupchris10
2
@ iupchris10 "Archivar no tiene variables de entorno" son las últimas palabras de mi respuesta, arriba. Eso, como digo en mi respuesta, es bueno . Es el punto .
mate
161

Se ifdefprodujo un cambio importante de reemplazo con Xcode 8. es decir, el uso de condiciones de compilación activa .

Consulte Crear y vincular en la nota de versión de Xcode 8 .

Nueva configuración de compilación

Nueva configuración: SWIFT_ACTIVE_COMPILATION_CONDITIONS

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

Anteriormente, teníamos que declarar sus marcas de compilación condicional en OTHER_SWIFT_FLAGS, recordando anteponer "-D" a la configuración. Por ejemplo, para compilar condicionalmente con un valor MYFLAG:

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

El valor para agregar a la configuración -DMYFLAG

Ahora solo necesitamos pasar el valor MYFLAG a la nueva configuración. ¡Es hora de mover todos esos valores de compilación condicional!

Consulte el siguiente enlace para obtener más funciones de configuración de compilación rápida en Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

DShah
fuente
¿Hay alguna forma de deshabilitar un conjunto de condiciones de compilación activa en el momento de la compilación? Necesito deshabilitar la condición DEBUG al construir la configuración de depuración para la prueba.
Jonny
1
@ Jonny La única forma que he encontrado es crear una tercera configuración de compilación para el proyecto. Desde el Proyecto> pestaña Información> Configuraciones, presione '+', luego duplique Depuración. Luego puede personalizar las condiciones de compilación activa para esta configuración. ¡No olvide editar sus esquemas Target> Test para usar la nueva configuración de compilación!
matthias
1
Esta debería ser la respuesta correcta ... ¡es lo único que me funcionó en xCode 9 usando Swift 4.x!
shokaveli
1
Por cierto, en Xcode 9.3 Swift 4.1 DEBUG ya está allí en condiciones de compilación activa y no tiene que agregar nada para verificar la configuración de DEBUG. Solo #if DEBUG y #endif.
Denis Kutlubaev
Creo que esto es fuera de tema y algo malo que hacer. no desea deshabilitar las condiciones de compilación activa. necesita una configuración nueva y diferente para las pruebas, que NO tendrá la etiqueta "Debug". Aprende sobre los esquemas.
Motti Shneor
93

A partir de Swift 4.1, si todo lo que necesita es verificar si el código está construido con la configuración de depuración o liberación, puede usar las funciones integradas:

  • _isDebugAssertConfiguration()(verdadero cuando la optimización se establece en -Onone)
  • _isReleaseAssertConfiguration()(verdadero cuando la optimización se establece en -O) (no disponible en Swift 3+)
  • _isFastAssertConfiguration()(verdadero cuando la optimización se establece en -Ounchecked)

p.ej

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

En comparación con las macros de preprocesador,

  • ✓ No necesita definir una -D DEBUGbandera personalizada para usarla
  • ~ En realidad se define en términos de configuración de optimización, no en la configuración de compilación de Xcode
  • ✗ Sin documentar, lo que significa que la función se puede eliminar en cualquier actualización (pero debe ser segura para AppStore ya que el optimizador las convertirá en constantes)

  • ✗ El uso de if / else siempre generará una advertencia de "Nunca se ejecutará".

kennytm
fuente
1
¿Se evalúan estas funciones integradas en tiempo de compilación o tiempo de ejecución?
ma11hew28
@MattDiPasquale Tiempo de optimización. if _isDebugAssertConfiguration()será evaluado if falseen modo de lanzamiento y if truees modo de depuración.
kennytm
2
Sin embargo, no puedo usar estas funciones para excluir algunas variables solo de depuración en el lanzamiento.
Franklin Yu
3
¿Estas funciones están documentadas en alguna parte?
Tom Harrington
77
A partir de Swift 3.0 y XCode 8, estas funciones no son válidas.
CodeBender el
87

Xcode 8 y superior

Utilice la compilación condiciones activas configuración en configuración de generación / Swift compilador - banderas personalizadas .

  • Esta es la nueva configuración de compilación para pasar banderas de compilación condicional al compilador Swift.
  • Banderas add simple como esto: ALPHA, BETAetc.

Luego verifíquelo con condiciones de compilación como esta:

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

Consejo: también puedes usar #if !ALPHAetc.

Jakub Truhlář
fuente
77

No hay un preprocesador Swift. (Por un lado, la sustitución de código arbitrario rompe la seguridad de tipo y memoria).

Sin embargo, Swift incluye opciones de configuración de tiempo de compilación, por lo que puede incluir condicionalmente código para ciertas plataformas o estilos de compilación o en respuesta a las banderas que defina con -Dargumentos del compilador. Sin embargo, a diferencia de C, una sección compilada condicionalmente de su código debe estar sintácticamente completa. Hay una sección sobre esto en Uso de Swift con cacao y Objective-C .

Por ejemplo:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
rickster
fuente
34
"Por un lado, la sustitución de código arbitrario rompe la seguridad de tipo y memoria". ¿Un preprocesador no hace su trabajo antes que el compilador (de ahí el nombre)? Por lo tanto, todos estos controles aún podrían tener lugar.
Thilo
10
@Thilo Creo que lo que rompe es el soporte IDE
Aleksandr Dubinsky
1
Creo que a lo que se está refiriendo @rickster es que las macros del preprocesador C no comprenden el tipo y su presencia rompería los requisitos de tipo de Swift. La razón por la que las macros funcionan en C es porque C permite la conversión de tipo implícito, lo que significa que puede colocar su INT_CONSTlugar en cualquier lugar donde floatsea ​​aceptado. Swift no permitiría esto. Además, si pudieras hacerlo var floatVal = INT_CONSTinevitablemente, se descompondría en algún momento más tarde cuando el compilador espera un Intpero lo usas como un Float(tipo de floatValse infiere como Int). 10 lanzamientos más tarde y es más limpio para eliminar macros ...
Ephemera
Estoy tratando de usar esto, pero no parece funcionar, todavía está compilando el código de Mac en las compilaciones de iOS. ¿Hay otra pantalla de configuración en algún lugar que deba modificarse?
Maury Markowitz
1
@Thilo tienes razón: un preprocesador no rompe ningún tipo de seguridad de memoria.
tcurdt
50

Mis dos centavos para Xcode 8:

a) Una marca personalizada que usa el -Dprefijo funciona bien, pero ...

b) Uso más simple:

En Xcode 8 hay una nueva sección: "Condiciones de compilación activa", ya con dos filas, para depurar y liberar.

Simplemente agregue su definición SIN -D.

ingconti
fuente
Gracias por mencionar que hay DOS FILAS DE DEPURACIÓN Y LIBERACIÓN
Yitzchak,
alguien probó esto en el lanzamiento?
Glenn
Esta es la respuesta actualizada para usuarios rápidos. es decir, sin -D.
Mani
46

isDebug Constant basado en condiciones de compilación activa

Otra solución, quizás más simple, que todavía da como resultado un valor booleano que puede pasar a funciones sin #ifcondicionar los condicionales en toda su base de código es definir DEBUGcomo uno de los objetivos de compilación de su proyecto Active Compilation Conditionse incluir lo siguiente (lo defino como una constante global):

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

Constante isDebug basada en la configuración de optimización del compilador

Este concepto se basa en la respuesta de kennytm

La principal ventaja cuando se compara con los de kennytm es que esto no depende de métodos privados o indocumentados.

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

En comparación con las macros del preprocesador y la respuesta de kennytm ,

  • ✓ No necesita definir una -D DEBUGbandera personalizada para usarla
  • ~ En realidad se define en términos de configuración de optimización, no en la configuración de compilación de Xcode
  • Documentado , lo que significa que la función seguirá los patrones normales de liberación / desaprobación de API.

  • ✓ El uso de if / else no generará una advertencia de "Nunca se ejecutará".

Jon Willis
fuente
25

La respuesta de Moignans aquí funciona bien. Aquí hay otra paz de información en caso de que ayude,

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

Puede negar las macros como a continuación,

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

En proyectos Swift creados con Xcode Versión 9.4.1, Swift 4.1

#if DEBUG
#endif

funciona de forma predeterminada porque en el preprocesador Macros DEBUG = 1 ya ha sido configurado por Xcode.

Entonces puede usar #if DEBUG "fuera de la caja".

Por cierto, cómo usar los bloques de compilación de condiciones en general está escrito en el libro de Apple The Swift Programming Language 4.1 (la sección Declaraciones de control del compilador) y cómo escribir los indicadores de compilación y qué es la contraparte de las macros C en Swift está escrito en otro libro de Apple Uso de Swift con cacao y Objetivo C (en la sección Directivas de preprocesador)

Espero que en el futuro Apple escriba los contenidos más detallados y los índices de sus libros.

Vadim Motorine
fuente
17

XCODE 9 Y ARRIBA

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
Midhun p
fuente
3
wow esa es la abreviatura más fea que he visto: p
rmp251
7

Después de configurar DEBUG=1la GCC_PREPROCESSOR_DEFINITIONSConfiguración de compilación, prefiero usar una función para realizar estas llamadas:

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

Y luego solo encierre en esta función cualquier bloque que quiera omitir en las compilaciones de depuración:

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

La ventaja en comparación con:

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

Es que el compilador verifica la sintaxis de mi código, así que estoy seguro de que su sintaxis es correcta y compila.

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

Fuente

Adam Smaka
fuente
1
Esta no es una compilación condicional. Si bien es útil, es solo un simple y antiguo tiempo de ejecución condicional. El OP pregunta después del tiempo de compilación para fines de metaprogramación
Shayne
3
Simplemente agregue @inlinabledelante funcy esta sería la forma más elegante e idiomática para Swift. En las versiones de lanzamiento, tu code()bloque se optimizará y eliminará por completo. Una función similar se utiliza en el propio marco NIO de Apple.
mojuba
1

Esto se basa en la respuesta de Jon Willis que se basa en la afirmación, que solo se ejecuta en compilaciones de depuración:

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

Mi caso de uso es para registrar declaraciones impresas. Aquí hay un punto de referencia para la versión de lanzamiento en 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)" )

huellas dactilares:

Log: 0.0

Parece que Swift 4 elimina por completo la llamada a la función.

Warren Stringer
fuente
Elimina, ya que en elimina la llamada en su totalidad cuando no está en depuración, debido a que la función está vacía? Eso sería perfecto.
Johan