Substituição #ifdef no idioma Swift

735

Em C / C ++ / Objective C, você pode definir uma macro usando pré-processadores do compilador. Além disso, você pode incluir / excluir algumas partes do código usando pré-processadores do compilador.

#ifdef DEBUG
    // Debug-only code
#endif

Existe uma solução semelhante no Swift?

mxg
fonte
1
Como uma idéia, você poderia colocar isso no seu obj-c ponte cabeçalhos ..
Matej
42
Você realmente deve conceder uma resposta, pois você tem várias opções para escolher, e essa pergunta recebeu muitos votos positivos.
David H

Respostas:

1069

Sim, você pode fazer isso.

No Swift, você ainda pode usar as macros de pré-processador "# if / # else / # endif" (embora mais restritas), conforme documentos da Apple . Aqui está um exemplo:

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

Agora, você deve definir o símbolo "DEBUG" em outro lugar. Defina-o na seção "Swift Compiler - Custom Flags", na linha "Other Swift Flags". Você adiciona o símbolo DEBUG à -D DEBUGentrada.

Como de costume, você pode definir um valor diferente no Debug ou no Release.

Eu testei em código real e funciona; embora não pareça ser reconhecido em um playground.

Você pode ler meu post original aqui .


NOTA IMPORTANTE: -DDEBUG=1 não funciona. Só -D DEBUGfunciona. Parece que o compilador está ignorando um sinalizador com um valor específico.

Jean Le Moignan
fonte
41
Essa é a resposta correta, embora seja necessário observar que você só pode verificar a presença do sinalizador, mas não um valor específico.
Charles Harley
19
Nota adicional : Além de adicionar -D DEBUGcomo indicado acima, você também precisa definir DEBUG=1em Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros 26/03
38
Não consegui fazer isso funcionar até alterar a formatação para -DDEBUGesta resposta: stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros Não há necessidade de adicionar DEBUG=1a Preprocessor Macros, se você não quiser usá-lo em código Objective-C.
Derpoliuk
7
@Daniel Você pode usar operadores booleanos padrão (ex: `#if!
DEBUG`
353

Conforme declarado no Apple Docs

O compilador Swift não inclui um pré-processador. Em vez disso, tira proveito dos atributos em tempo de compilação, configurações de compilação e recursos de idioma para obter a mesma funcionalidade. Por esse motivo, as diretivas de pré-processador não são importadas no Swift.

Consegui alcançar o que queria usando configurações de compilação personalizadas:

  1. Vá para o seu projeto / selecione seu destino / Configurações de compilação / pesquise sinalizadores personalizados
  2. Para o destino escolhido, defina seu sinalizador personalizado usando o prefixo -D (sem espaços em branco), para Debug e Release
  3. Execute as etapas acima para cada alvo que você tiver

Veja como você verifica o destino:

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

insira a descrição da imagem aqui

Testado usando o Swift 2.2

Andrej
fonte
4
1. com o espaço em branco também funciona, 2. deve definir o sinalizador apenas para Depuração?
c0ming
3
@ c0ming depende de suas necessidades, mas se você deseja que algo aconteça apenas no modo de depuração, e não no release, é necessário remover -DDEBUG do Release.
precisa
1
Depois que eu defina o sinalizador de costume -DLOCAL, no meu #if LOCAl #else #endif, ele cai na #elseseção. Dupliquei o destino original AppTargete o renomeei para AppTargetLocal& defina seu sinalizador personalizado.
Perwyl Liu
3
@Andrej, você sabe como fazer o XCTest reconhecer os sinalizadores personalizados também? Sei que cai #if LOCAL , o resultado pretendido quando corro com o simulador e cai #else durante o teste. Eu quero que ele caia #if LOCALtambém durante os testes.
Perwyl Liu
3
Essa deve ser a resposta aceita. A resposta atual aceita está incorreta para Swift, pois se aplica apenas ao Objective-C.
Miken.mkndev 03/09/16
171

Em muitas situações, você realmente não precisa de compilação condicional ; você só precisa de um comportamento condicional que possa ser ligado e desligado. Para isso, você pode usar uma variável de ambiente. Isso tem a enorme vantagem de você não precisar recompilar.

Você pode definir a variável de ambiente e ativá-la ou desativá-la facilmente no editor de esquema:

insira a descrição da imagem aqui

Você pode recuperar a variável de ambiente com NSProcessInfo:

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

Aqui está um exemplo da vida real. Meu aplicativo é executado apenas no dispositivo, porque usa a biblioteca de músicas, que não existe no Simulador. Como, então, tirar capturas de tela no Simulador para dispositivos que não possuo? Sem essas capturas de tela, não posso enviar para a AppStore.

Preciso de dados falsos e uma maneira diferente de processá-los . Eu tenho duas variáveis ​​de ambiente: uma que, quando ativada, informa ao aplicativo para gerar os dados falsos a partir dos dados reais enquanto estiver sendo executado no meu dispositivo; o outro que, quando ligado, usa os dados falsos (e não a biblioteca de músicas que falta) durante a execução no Simulador. É fácil ativar ou desativar cada um desses modos especiais, graças às caixas de seleção de variáveis ​​de ambiente no editor de esquema. E o bônus é que não posso usá-los acidentalmente na minha compilação na App Store, porque o arquivamento não possui variáveis ​​de ambiente.

mate
fonte
Por alguma razão o meu variável de ambiente retornado como nulo no segundo lançamento da aplicação
Eugene
60
Cuidado : as variáveis ​​de ambiente são definidas para todas as configurações de compilação, não podem ser definidas para configurações individuais. Portanto, essa não é uma solução viável se você precisar que o comportamento mude, dependendo se é uma versão ou uma compilação de depuração.
Eric
5
@ Eric Concordou, mas eles não estão definidos para todas as ações do esquema. Portanto, você pode fazer uma coisa no build-and-run e outra diferente no archive, que geralmente é a distinção da vida real que você deseja desenhar. Ou você pode ter vários esquemas, que também são um padrão comum na vida real. Além disso, como eu disse na minha resposta, é fácil ativar e desativar variáveis ​​de ambiente em um esquema.
mate
10
Variáveis ​​de ambiente NÃO funcionam no modo de arquivamento. Eles são aplicados apenas quando o aplicativo é iniciado a partir do XCode. Se você tentar acessá-los em um dispositivo, o aplicativo falhará. Descobri da maneira mais difícil.
iupchris10
2
@ iupchris10 "O arquivamento não possui variáveis ​​de ambiente" são as últimas palavras da minha resposta acima. Isso, como digo na minha resposta, é bom . Esse é o ponto .
mate
160

Uma grande mudança de ifdefsubstituição ocorreu com o Xcode 8. ou seja, o uso das Condições de Compilação Ativas .

Consulte Construção e vinculação no Xcode 8 Release note .

Novas configurações de compilação

Nova configuração: SWIFT_ACTIVE_COMPILATION_CONDITIONS

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

Anteriormente, tínhamos que declarar seus sinalizadores de compilação condicional em OTHER_SWIFT_FLAGS, lembrando-se de acrescentar "-D" à configuração. Por exemplo, para compilar condicionalmente com um valor MYFLAG:

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

O valor a ser adicionado à configuração -DMYFLAG

Agora só precisamos passar o valor MYFLAG para a nova configuração. Hora de mover todos esses valores de compilação condicional!

Consulte o link abaixo para obter mais recursos do Swift Build Settings no Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

DShah
fonte
Existe alguma maneira de desabilitar um conjunto de condições de compilação ativa no momento da criação? Preciso desabilitar a condição DEBUG ao criar a configuração de depuração para teste.
Jonny
1
@ Jonny A única maneira que eu encontrei é criar uma terceira configuração para o projeto. Na guia Projeto> Informações> Configurações, pressione '+' e duplique a depuração. Você pode personalizar as Condições de compilação ativa para esta configuração. Não se esqueça de editar seus esquemas Target> Test para usar a nova configuração de compilação!
Matthias
1
Essa deve ser a resposta correta ... é a única coisa que funcionou para mim no xCode 9 usando o Swift 4.x!
shokaveli
1
Aliás, no Xcode 9.3, o Swift 4.1 DEBUG já está presente nas Condições de Compilação Ativas e você não precisa adicionar nada para verificar a configuração do DEBUG. Apenas #if DEBUG e #endif.
Denis Kutlubaev
Eu acho que isso é fora de tópico e uma coisa ruim a se fazer. você não deseja desativar as condições de compilação ativa. você precisa de uma configuração nova e diferente para teste - que NÃO terá a tag "Debug". Aprenda sobre esquemas.
Motti Shneor 18/06/19
93

A partir do Swift 4.1, se tudo o que você precisa é apenas verificar se o código foi criado com a configuração de depuração ou liberação, você pode usar as funções internas:

  • _isDebugAssertConfiguration() (verdadeiro quando a otimização está definida como -Onone)
  • _isReleaseAssertConfiguration()(verdadeiro quando a otimização está definida como -O) (não disponível no Swift 3+)
  • _isFastAssertConfiguration()(verdadeiro quando a otimização está definida como -Ounchecked)

por exemplo

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

Comparado com macros de pré-processador,

  • ✓ Você não precisa definir um -D DEBUGsinalizador personalizado para usá-lo
  • ~ Na verdade, é definido em termos de configurações de otimização, não na configuração de compilação do Xcode
  • ✗ Não documentado, o que significa que a função pode ser removida em qualquer atualização (mas deve ser segura na AppStore, pois o otimizador as transformará em constantes)

  • ✗ Usar if / else sempre gerará um aviso "Nunca será executado".

kennytm
fonte
1
Essas funções internas são avaliadas em tempo de compilação ou tempo de execução?
Ma11hew28 28/02
@MattDiPasquale Tempo de otimização. if _isDebugAssertConfiguration()será avaliado if falseno modo de liberação e if trueé o modo de depuração.
Kennytm
2
No entanto, não posso usar essas funções para desativar alguma variável somente de depuração no lançamento.
31816 Franklin Yu
3
Essas funções estão documentadas em algum lugar?
Tom Harrington
7
A partir do Swift 3.0 e do XCode 8, essas funções são inválidas.
CodeBender
87

Xcode 8 e acima

Use a configuração Condições de compilação ativa em Configurações de compilação / Compilador Swift - sinalizadores personalizados .

  • Esta é a nova configuração de compilação para passar sinalizadores de compilação condicional para o compilador Swift.
  • Flags Adicionar simples como este: ALPHA, BETAetc.

Em seguida, verifique-o com condições de compilação como esta:

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

Dica: você também pode usar #if !ALPHAetc.

Jakub Truhlář
fonte
77

Não há pré-processador Swift. (Por um lado, a substituição arbitrária de código quebra a segurança de tipo e de memória.)

No entanto, o Swift inclui opções de configuração em tempo de construção, para que você possa incluir condicionalmente código para determinadas plataformas ou estilos de construção ou em resposta a sinalizadores definidos com -Dargumentos do compilador. Ao contrário do C, porém, uma seção compilada condicionalmente do seu código deve estar sintaticamente concluída. Há uma seção sobre isso em Usando o Swift With Cocoa e o Objective-C .

Por exemplo:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
rickster
fonte
34
"Por um lado, a substituição arbitrária de códigos quebra a segurança de tipo e memória." Um pré-processador não funciona antes do compilador (daí o nome)? Portanto, todas essas verificações ainda podem ocorrer.
Thilo
10
@Thilo Acho que o que ele quebra é o suporte IDE
Aleksandr Dubinsky
1
Acho que o que o @rickster está dizendo é que as macros do pré-processador C não têm entendimento do tipo e sua presença quebraria os requisitos de tipo do Swift. A razão pela qual as macros funcionam em C é porque C permite a conversão implícita de tipos, o que significa que você pode colocar seu local em INT_CONSTqualquer lugar floatque seja aceito. Swift não permitiria isso. Além disso, se você pudesse fazê var floatVal = INT_CONST-lo inevitavelmente, ele seria interrompido em algum momento mais tarde, quando o compilador espera um, Intmas você o usa como um Float(o tipo de floatValseria inferido como Int). 10 elencos mais tarde e é apenas mais limpo para macros Remover ...
Ephemera
Estou tentando usar isso, mas parece que não funciona, ainda está compilando o código do Mac nas versões do iOS. Existe outra tela de configuração em algum lugar que precise ser ajustada?
Maury Markowitz
1
@Estilo de que você está correto - um pré-processador não quebra nenhum tipo ou segurança de memória.
tcurdt
50

Meus dois centavos para o Xcode 8:

a) Um sinalizador personalizado usando o -D prefixo funciona bem, mas ...

b) Uso mais simples:

No Xcode 8, há uma nova seção: "Condições de Compilação Ativas", já com duas linhas, para depuração e lançamento.

Basta adicionar sua definição SEM -D.

ingconti
fonte
Obrigado por mencionar que existem duas linhas para depuração e versão
Yitzchak
alguém testou isso no lançamento?
Glenn
Esta é a resposta atualizada para usuários rápidos. ou seja, sem -D.
Mani
46

Constante isDebug com base nas condições de compilação ativa

Outra solução, talvez mais simples, que ainda resulta em um booleano que você pode passar para funções sem aplicar #ifcondicionais em toda a base de código é definir DEBUGcomo um dos objetivos de construção do projeto Active Compilation Conditionse incluir o seguinte (eu a defino como uma constante global):

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

Constante isDebug com base nas configurações de otimização do compilador

Este conceito baseia-se na resposta de kennytm

A principal vantagem ao comparar com o kennytm é que isso não depende de métodos particulares ou não documentados.

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

Comparado com as macros do pré-processador e a resposta do kennytm ,

  • ✓ Você não precisa definir um -D DEBUGsinalizador personalizado para usá-lo
  • ~ Na verdade, é definido em termos de configurações de otimização, não na configuração de compilação do Xcode
  • Documentado , o que significa que a função seguirá os padrões normais de liberação / descontinuação da API.

  • ✓ O uso de if / else não gerará um aviso "Nunca será executado".

Jon Willis
fonte
25

Moignans responder aqui funciona bem. Aqui está outra informação, caso ajude,

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

Você pode negar as macros como abaixo,

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

Nos projetos Swift criados com o Xcode Versão 9.4.1, Swift 4.1

#if DEBUG
#endif

funciona por padrão porque nas macros do pré-processador DEBUG = 1 já foi definido pelo Xcode.

Então você pode usar #if DEBUG "fora da caixa".

A propósito, como usar os blocos de compilação de condições em geral está escrito no livro da Apple, The Swift Programming Language 4.1 (a seção Declarações de Controle do Compilador) e como escrever os sinalizadores de compilação e o que é equivalente às macros C do Swift. outro livro da Apple, usando Swift com cacau e objetivo C (na seção Diretivas de pré-processador)

Esperança no futuro A Apple escreverá o conteúdo mais detalhado e os índices de seus livros.

Vadim Motorine
fonte
17

XCODE 9 E ACIMA

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
midhun p
fonte
3
uau, que é a abreviação mais feia que eu já vi: p
rmp251 14/02
7

Após definir DEBUG=1as GCC_PREPROCESSOR_DEFINITIONSconfigurações de compilação, prefiro usar uma função para fazer isso:

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

E então apenas coloque nesta função qualquer bloco que eu queira omitir nas compilações de depuração:

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

A vantagem quando comparada com:

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

É que o compilador verifica a sintaxe do meu código, por isso tenho certeza de que a sintaxe está correta e se compila.

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

Fonte

Adam Smaka
fonte
1
Isso não é compilação condicional. Embora útil, é apenas um condicional de execução simples e antigo. O OP está pedindo compiletime para fins de metaprogramação
Shayne
3
Basta adicionar @inlinablena frente funce essa seria a maneira mais elegante e idiomática do Swift. Nas versões de lançamento, seu code()bloco será otimizado e eliminado por completo. Uma função semelhante é usada na estrutura NIO da Apple.
Mojuba 12/05/19
1

Isso se baseia na resposta de Jon Willis que se baseia na afirmação, que só é executada nas compilações de depuração:

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

Meu caso de uso é para registrar instruções de impressão. Aqui está uma referência para a versão Release no 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)" )

impressões:

Log: 0.0

Parece que o Swift 4 elimina completamente a chamada de função.

Warren Stringer
fonte
Elimina, como em remove a chamada na sua totalidade quando não estiver em depuração - devido à função estar vazia? Seria perfeito.
24418 Johan Johan