Conditional Compilation em aplicativos móveis — essência, diretivas e princípio de funcionamento

Autor: IT Sectr Publicado: 2026-06-01 Tempo de leitura: 9 min

Conditional Compilation permite ao compilador incluir ou pular partes do código-fonte dependendo de condições conhecidas no momento da compilação. De acordo com The Swift Programming Language (2026), a diretiva #if é processada durante a análise AST antes da geração de código de máquina. Conditional Compilation oferece aos desenvolvedores a capacidade de manter uma base de código única para múltiplas plataformas e configurações sem duplicação.

Principais pontos

  • Conditional Compilation — técnica de compilação seletiva de código de acordo com condições de plataforma, configuração ou versão da linguagem.
  • Diretivas #if, #elseif, #else, #endif — as principais construções de compilação condicional em Swift, C, C++, Objective-C.
  • Kotlin não possui diretivas de pré-processador — em vez disso, são usados BuildConfig, expect/actual e sourceSets.
  • Vantagem — o código para plataformas inadequadas não é compilado, reduzindo o tamanho do binário e eliminando erros.
  • iOS/macOS código compartilhado — Conditional Compilation é a base do desenvolvimento de frameworks multiplataforma da Apple.

O que é Conditional Compilation

Conditional Compilation é um mecanismo onde o compilador analisa as diretivas de compilação condicional e inclui no binário de saída apenas os blocos de código cujas condições são atendidas. Isso permite ter uma única base de código que se adapta a diferentes plataformas e configurações alvo.

O conceito veio de C/C++ com as diretivas de pré-processador #ifdef, #ifndef, #endif. Em linguagens modernas (Swift, Rust, Go), o mecanismo funciona no nível do compilador sem um pré-processador separado, o que aumenta a segurança: blocos condicionais devem ser sintaticamente corretos mesmo se não forem compilados.

De acordo com a sessão da Apple WWDC “Embrace Swift” (2025), cerca de 40% dos projetos Swift usam compilação condicional para suportar iOS e macOS em um único alvo. Para projetos com UIKit e SwiftUI, o código de UI é frequentemente dividido por diretivas #if os(iOS) e #if os(macOS), permitindo reutilizar a lógica de negócios.

A principal vantagem é a segurança em tempo de compilação. O código para uma plataforma inadequada não apenas não é executado, mas não é compilado. Isso significa que erros no código específico do iOS não aparecerão ao compilar para macOS, e vice-versa. Verificações em tempo de execução não oferecem tais garantias.

Conditional Compilation em Swift

Swift fornece quatro diretivas principais: #if, #elseif, #else, #endif. Diferente do pré-processador C, Swift exige correção sintática do código em todos os ramos — o compilador analisa todo o código, mas gera código de máquina apenas para os ramos ativos.

Verificações de plataforma os()

Swift suporta funções de verificação integradas: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Essas funções verificam a plataforma alvo para a qual o aplicativo está sendo compilado. A combinação com && e || permite criar condições complexas.

swift
// Código unificado para iOS, macOS e tvOS
import Foundation

class PlatformService {
    func getSystemVersion() -> String {
        #if os(iOS) || os(tvOS)
            return UIDevice.current.systemVersion
        #elseif os(macOS)
            let vers = ProcessInfo.processInfo.operatingSystemVersion
            return "\(vers.majorVersion).\(vers.minorVersion)"
        #else
            return "unknown"
        #endif
    }
}

Verificações de versão do compilador

Swift suporta verificação de versão do compilador: #if swift(>=5.9). Isso é útil para bibliotecas e frameworks que suportam múltiplas versões do Swift. Novos recursos da linguagem (como macros no Swift 5.9) podem ser protegidos por tal verificação.

swift
// Compatibilidade retroativa
#if swift(>=5.9)
    @MainActor
    struct ModernView: View {
        var body: some View {
            Text("Modern SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Legacy SwiftUI")
        }
    }
#endif

Verificação de disponibilidade de módulo canImport()

A função canImport(ModuleName) verifica se o módulo especificado está disponível no ambiente de compilação atual. Este é o mecanismo mais flexível: não está vinculado a uma plataforma específica. Por exemplo, código usando CoreHaptics será compilado apenas em dispositivos onde este framework estiver disponível.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Implementação de resposta háptica
        }
    }
#endif

Alternativas em Kotlin e Android

Kotlin como linguagem não possui diretivas de pré-processador. Em vez disso, o ecossistema Android oferece três alternativas: campos BuildConfig (verificações em tempo de execução), sourceSets (substituição de arquivos inteiros) e expect/actual (no Kotlin Multiplatform).

Source Sets no Gradle

Os sourceSets do Gradle permitem ter diferentes implementações de classes para diferentes flavors ou tipos de compilação. No diretório src/debug/ está a implementação para debug, em src/release/ — para release. Ao compilar, o Gradle seleciona o sourceSet apropriado e compila apenas seus arquivos.

kotlin
// src/debug/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        Log.d(tag, message)
    }
}

// src/release/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        // No-op no release
    }
}

Expect/Actual no Kotlin Multiplatform

O KMP fornece o mecanismo expect (declaração no código comum) e actual (implementação para uma plataforma específica). É um mecanismo em tempo de compilação: para iOS, a implementação actual do sourceSet iOS é compilada; para Android — do sourceSet Android. Implementações não alvo não são compiladas.

kotlin
// commonMain — declaração expect
expect fun getPlatformName(): String

// androidMain — actual para Android
actual fun getPlatformName(): String =
    "Android \${Build.VERSION.SDK_INT}"

// iosMain — actual para iOS
actual fun getPlatformName(): String =
    UIDevice.current.systemName() + " " + UIDevice.current.systemVersion

Pré-processador C/C++ e NDK

Ao desenvolver bibliotecas nativas através do Android NDK, o pré-processador clássico C/C++ com as diretivas #ifdef, #ifndef, #define é usado. Diferente do Swift, o pré-processador C funciona no nível de texto — o código em ramos inativos pode ser sintaticamente incorreto.

Flags de plataforma NDK

O NDK define macros para cada plataforma: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Para arquiteturas: __arm__, __aarch64__, __x86_64__. Esses macros são definidos pelo compilador automaticamente ao compilar para a plataforma alvo.

cpp
// Código nativo para Android e iOS
#include <cstdint>

#ifdef __ANDROID__
    int32_t getJniEnv(JNIEnv* env) {
        return env->GetVersion();
    }
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    int32_t getOsVersion() {
        #if TARGET_OS_IOS
            return "iOS";
        #elif TARGET_OS_OSX
            return "macOS";
        #endif
    }
#endif

Ao trabalhar com NDK, é importante lembrar que o pré-processador C/C++ é uma substituição de texto. Se houver um erro sintático em um ramo inativo, o compilador não o verá, mas se um #define incorreto quebrar um ramo ativo — o erro aparecerá. Recomenda-se minimizar cadeias de #define e usar constantes constexpr.

Para Rust, que também é usado no desenvolvimento móvel através de UniFFI e Mozilla Application Services, existe seu próprio mecanismo — feature flags no Cargo.toml. Flags como #[cfg(target_os = "android")] em Rust funcionam de maneira similar às diretivas Swift: a verificação é feita no nível do compilador, não do pré-processador. Isso torna Rust uma escolha atraente para bibliotecas nativas que devem compilar para Android e iOS a partir de uma única base de código.

Cenários práticos e antipadrões

Conditional Compilation é eficaz em cenários estritamente definidos. Quando usada incorretamente, cria código com cheiro (code smell) difícil de testar e manter. Vamos considerar os cenários corretos e erros típicos.

Cenários corretos

O primeiro cenário é a abstração de plataforma: uma única Fachada com Conditional Compilation selecionando a implementação de plataforma internamente. O segundo é a depuração e criação de perfil: ferramentas de desenvolvimento que não devem ir para o lançamento. O terceiro é a compatibilidade retroativa: suporte para versões antigas do SO até que a versão mínima seja atualizada.

CenárioLinguagemCondição
Abstração de plataformaSwift#if os(iOS)
DepuraçãoSwift/ObjC#if DEBUG
Compatibilidade retroativaSwift#if swift(>=5.7)
Biblioteca nativaC/C++#ifdef __ANDROID__
Teste A/BJava/KotlinBuildConfig.FLAVOR

Antipadrões

O antipadrão mais perigoso é a proliferação de diretivas por todo o código. Se cada segundo arquivo contém #if, é um sinal de que a arquitetura precisa de refatoração. A solução correta é extrair o código de plataforma para trás de protocolos/interfaces e usar Injeção de Dependência.

  • #if em cada arquivo — um antipadrão arquitetural. O código de plataforma deve ser isolado atrás de protocolos.
  • #if aninhados — rapidamente se tornam ilegíveis. A profundidade de aninhamento não deve exceder 2 níveis.
  • Duplicar funções inteiras — se uma função é completamente copiada em #if e #else, ela deve ser extraída para uma parte comum.
  • Testes — o código dentro de ramos inativos não é testado. São necessárias compilações CI de todas as combinações possíveis.
  • Flags mágicas — flags não documentadas que a nova equipe de desenvolvimento desconhece.

Perguntas frequentes

Como Conditional Compilation difere das verificações em tempo de execução?

Conditional Compilation funciona em tempo de compilação: código inativo não vai para o binário. Verificações em tempo de execução (if / switch) são sempre compiladas, a condição é verificada durante a execução. O primeiro é mais seguro e eficiente, o segundo é mais flexível (pode ser alterado sem recompilar).

Pode-se usar #if dentro de uma função em Swift?

Sim, Swift permite diretivas #if dentro de funções, loops e até dentro de expressões. Este é um dos recursos que faltavam nas primeiras versões do Swift. Por exemplo: let x = #if DEBUG 1 #else 0 #endif — código válido.

Por que Kotlin não adicionou um pré-processador?

Os desenvolvedores do Kotlin recusaram deliberadamente o pré-processador, considerando-o uma fonte de código frágil. Em vez disso, eles oferecem expect/actual (segurança em tempo de compilação) e sourceSets do Gradle (isolamento no nível de arquivos). Ambas as abordagens são mais confiáveis que a substituição de texto.

Como testar código dentro de ramos #if inativos?

Compile o aplicativo com diferentes combinações de flags no CI. Para Swift: configure esquemas Xcode separados com diferentes Active Compilation Conditions. Para Android: configure Build Variants separados e execute testes para cada um. A automação é obrigatória.

O que acontece se a condição #if contiver um erro sintático?

Em Swift, a condição #if é uma diretiva do compilador. Se a própria condição for sintaticamente incorreta (por exemplo, um erro de digitação no nome os()), o compilador emitirá um erro de compilação. Em C/C++, o pré-processador simplesmente não encontrará o macro e a condição se tornará falsa.

Resumo

  • Conditional Compilation — técnica de compilação que exclui código não alvo em tempo de compilação, ao contrário das verificações em tempo de execução.
  • Swift suporta #if com os(), canImport(), swift() — diretivas seguras em tempo de compilação que exigem correção sintática de todos os ramos.
  • Kotlin usa expect/actual e sourceSets do Gradle em vez de um pré-processador — abordagens mais confiáveis mas menos flexíveis.
  • C/C++ no NDK usa o pré-processador de texto clássico #ifdef / #ifndef com macros de plataforma __ANDROID__, __APPLE__.
  • Uso correto — abstração de plataforma, depuração, compatibilidade retroativa. Uso incorreto — #if em cada arquivo, aninhamento profundo, flags mágicas.
  • CI é obrigatório — todas as combinações de flags devem ser compiladas e testadas automaticamente, caso contrário o código em ramos inativos se torna morto.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também