Active Compilation Conditions in App Development: Key Concepts and Applications

Author: IT Sectr Published: 2026-06-01 Reading time: 8 min

Active Compilation Conditions are flags passed to the compiler at build time that allow including or excluding specific code blocks from the final binary file. According to Apple Developer Documentation (2026), Swift supports Active Compilation Conditions through the OTHER_SWIFT_FLAGS key and the #if directive. Active Compilation Conditions give developers the ability to build different versions of code for debugging, testing, and production without runtime checks.

Key Takeaways

  • Active Compilation Conditions — custom compilation flags that determine which code blocks are compiled into the final binary.
  • Swift uses the OTHER_SWIFT_FLAGS key in Xcode Build Settings to set flags with the -D prefix.
  • The #if directive checks for the presence of a flag: code inside #if DEBUG compiles only in debug builds.
  • Android has an analogous capability — BuildConfig fields and productFlavors in Gradle.
  • Performance — conditional compilation leaves no traces in the release binary, unlike runtime flags.

What Are Active Compilation Conditions

Active Compilation Conditions are compilation flags that determine the set of active preprocessor directives at build time. Unlike runtime flags (checking if (isDebug)), compilation conditions physically exclude inactive code from the binary file, resulting in performance gains and reduced application size.

The mechanism operates at the preprocessor or early compilation phase level: the compiler receives a list of active names, and when it encounters the #if NAME directive, it checks whether NAME is in that list. If the name is not present, the code inside the block is ignored and not compiled.

According to the Swift.org Blog (2025), using Active Compilation Conditions instead of runtime flags reduces release binary size by an average of 12-18% for projects with extensive logging and debugging tools. This is especially critical for mobile applications with installation file size constraints.

The main difference from conditional compilation at the C/C++ preprocessor level is that Active Compilation Conditions in Swift and Kotlin operate at the compiler’s AST (Abstract Syntax Tree) level, not at the text replacement level. This makes them safer and more predictable: any syntax error in an inactive #if branch will be detected during parsing, not manifest at runtime.

Another important difference is that in Swift, the condition #if os(iOS) || os(macOS) is checked at compile time and works with platform names, not preprocessor macros. This eliminates a whole class of bugs related to incorrect text insertion via #define, which are possible in the C/C++ preprocessor. The Swift compiler sees the AST, not replaced text, making debugging conditional compilation significantly easier.

Active Compilation Conditions in Swift

Swift supports Active Compilation Conditions through the #if directive, which accepts a list of flag names combined with logical operators &&, ||, and !. The compiler includes code inside #if ... #endif only if the condition is true.

Built-in Swift Conditions

Swift provides several built-in conditions: DEBUG (automatically active in debug builds), swift(>=5.0) (compiler version check), canImport(UIKit) (module availability check), and targetEnvironment(simulator) (environment check). These conditions require no additional configuration.

swift
// Built-in Swift Conditions
#if DEBUG
    print("Debug build — logging active")
#endif

#if canImport(UIKit)
    import UIKit
    let screen = UIScreen.main.bounds
#elseif canImport(AppKit)
    import AppKit
    let screen = NSScreen.main?.frame
#endif

Custom Flags in Xcode

Developers can add their own flags through the Build Setting OTHER_SWIFT_FLAGS in Xcode. The flag is specified with the -D prefix, for example -DBETA or -DANALYTICS_ENABLED. Different flag sets can be configured for different configurations (Debug, Release, Staging).

swift
// Handling custom BETA flag
#if BETA
    let apiEndpoint = "https://beta.api.com"
    let isLoggingEnabled = true
#else
    let apiEndpoint = "https://api.com"
    let isLoggingEnabled = false
#endif

func trackEvent(_ name: String) {
    #if ANALYTICS_ENABLED
        Analytics.log(name)
    #endif
}

Platform Conditions #if os()

Swift supports platform conditions: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). These conditions check the target build platform and allow writing code shared across multiple Apple platforms with platform-specific blocks.

swift
import Foundation

func getDeviceName() -> String {
    #if os(iOS)
        return UIDevice.current.name
    #elseif os(macOS)
        return Host.current.name ?? "Unknown"
    #else
        return "Other platform"
    #endif
}

Android and Kotlin Alternatives

In the Android ecosystem, Active Compilation Conditions are implemented through the BuildConfig system, productFlavors, and flags in build.gradle.kts. Kotlin does not have a direct equivalent of the #if directive at the language level but provides alternative mechanisms.

BuildConfig Fields as Flags

The most common approach is to add a buildConfigField for each flag: buildConfigField("boolean", "BETA", "true"). These fields are generated into the BuildConfig class for each Build Variant separately. The DEBUG field is already built-in and automatically true for debug builds.

kotlin
// build.gradle.kts
android {
    buildTypes {
        debug {
            buildConfigField("boolean", "BETA", "true")
        }
        release {
            buildConfigField("boolean", "BETA", "false")
        }
    }
}

// Usage in Kotlin code
if (BuildConfig.BETA) {
    enableBetaFeatures()
}

Source Sets and productFlavors

Gradle allows creating separate sourceSets directories for each flavor. For example, src/demo/ and src/full/. Classes with the same name in different sourceSets replace each other when building the corresponding flavor. This is a more powerful mechanism than flags because entire classes can be overridden.

kotlin
// src/demo/java/com/example/Config.kt
object Config {
    const val API_URL = "http://demo.api.com"
    const val IS_BETA = true
}

// src/full/java/com/example/Config.kt
object Config {
    const val API_URL = "https://full.api.com"
    const val IS_BETA = false
}

For Kotlin Multiplatform (KMP), the expect/actual directive is available, which allows declaring expected declarations in common code and providing platform-specific implementations. This is a compiler-level mechanism similar to Active Compilation Conditions in effect — inactive code is not compiled for unsuitable platforms.

Use Cases and Best Practices

Active Compilation Conditions are used in four main scenarios: debugging (logs, inspectors), A/B testing (feature flags), platform adaptation (iOS/macOS shared code), and licensing (free/paid versions).

Debug Logging

The most common scenario is conditional logging. In debug builds, all logs are written to the console; in release builds, nothing is logged. Using #if DEBUG or BuildConfig.DEBUG ensures that the release binary contains not a single logger call, even inlined ones.

swift
func logRequest(_ url: URL, _ statusCode: Int) {
    #if DEBUG
        Logger.debug("Request to \(url.absoluteString) returned \(statusCode)")
    #endif
}

Feature Flags at Build Time

If a new feature is not yet ready for production but already exists in the code, it can be hidden behind a compilation flag. Unlike runtime feature flags, compilation flags do not burden the application with checks and cannot be enabled by the user.

  • New features — hide unfinished functionality until the next release without deleting code
  • Analytics — enable additional metrics collection only for beta testers
  • Third-party SDKs — exclude heavy libraries from the free version of the application
  • UI components — show experimental screens only in staging builds

Best Practices

Active Compilation Conditions should be used sparingly. An excessive number of flags makes the code difficult to understand: a developer cannot be sure which branches will compile at a given moment. It is recommended to document each flag in the README or a dedicated CONFIG.md file.

In large projects with distributed teams, it is useful to implement automatic flag validation in CI. Each pull request should pass builds with all possible combinations of Active Compilation Conditions. This ensures that code under an inactive flag has not broken due to refactoring, and no conditional compilation branch remains untested until release. Tools like xcresulttool (for iOS) and Gradle Build Scan (for Android) help automate this process.

  • Minimum flags — no more than 5-7 active conditions per project. Each flag is a point of complexity.
  • Naming convention — all flags in UPPER_CASE, with project prefix: MYAPP_BETA, MYAPP_ANALYTICS.
  • Code review — each addition of #if or buildConfigField must go through a separate review.
  • Testing — CI should build all possible flag combinations at least once a day.

Frequently Asked Questions

What is the difference between #if DEBUG and if (isDebug) in Swift?

#if DEBUG is a compilation directive: if DEBUG is not active, the code inside the block does not go into the binary. if (isDebug) is a runtime check: the code is always compiled, the condition is checked at runtime. #if leaves no traces in the release build.

How do I add a custom flag in Xcode?

In the project’s Build Settings, find Other Swift Flags (OTHER_SWIFT_FLAGS) and add a new line with the flag: -DMY_FLAG. The flag will be visible to the #if MY_FLAG directive. Different flags can be set for Debug and Release configurations.

Is there a Kotlin equivalent of Swift #if?

Kotlin/JVM does not have a direct equivalent. Instead, BuildConfig fields are used (runtime check, but ProGuard may remove unused code). In Kotlin Multiplatform — the expect/actual directive at the declaration level.

Can multiple flags be combined in one #if?

Yes, Swift supports logical operators: #if DEBUG && BETA, #if os(iOS) || os(tvOS), #if !RELEASE. Conditions can be grouped with parentheses for complex logic. AND and OR work by standard short-circuit rules.

Why doesn’t #if DEBUG work in SwiftUI previews?

SwiftUI previews are built in a separate process with flags different from the main target. DEBUG may not be active. Solution: use targetEnvironment(simulator) for preview code or extract conditional logic into separate methods.

Summary

  • Active Compilation Conditions — compilation flags that physically exclude inactive code from the binary without runtime checks.
  • Swift supports #if with built-in conditions (DEBUG, os, canImport) and custom flags via OTHER_SWIFT_FLAGS.
  • Android and Kotlin use BuildConfig fields, productFlavors, and the expect/actual mechanism in KMP.
  • Performance — conditional compilation reduces binary size by 12-18% in projects with extensive logging.
  • Feature flags at compile time do not burden the application with checks and cannot be enabled by the user.
  • Recommendations — no more than 5-7 flags per project, naming convention with prefix, mandatory testing of all combinations in CI.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also