Active Compilation Conditions у розробці застосунків: ключові поняття та застосування

Автор: IT Sectr Опубліковано: 2026-06-01 Час читання: 8 хв

Active Compilation Conditions — це прапорці, які передаються компілятору на етапі збірки та дозволяють включати або виключати певні блоки коду з фінального бінарного файлу. За даними Apple Developer Documentation (2026), Swift підтримує Active Compilation Conditions через ключ OTHER_SWIFT_FLAGS та директиву #if. Active Compilation Conditions дають розробникам можливість збирати різні версії коду для налагодження, тестування та продакшену без перевірок під час виконання.

Головне

  • Active Compilation Conditions — користувацькі прапорці компіляції, які визначають, які блоки коду компілюються у фінальний бінарник.
  • Swift використовує ключ OTHER_SWIFT_FLAGS у Xcode Build Settings для встановлення прапорців із префіксом -D.
  • Директива #if перевіряє наявність прапорця: код всередині #if DEBUG компілюється лише в debug-збірці.
  • Android має аналогічну можливість — поля BuildConfig та productFlavors у Gradle.
  • Продуктивність — умовна компіляція не залишає слідів у release-бінарнику, на відміну від прапорців часу виконання.

Що таке Active Compilation Conditions

Active Compilation Conditions — це прапорці компіляції, які визначають набір активних директив препроцесора на етапі збірки. На відміну від прапорців часу виконання (перевірка if (isDebug)), умови компіляції фізично виключають неактивний код із бінарного файлу, що дає приріст продуктивності та зменшує розмір застосунку.

Механізм працює на рівні препроцесора або ранніх фаз компіляції: компілятор отримує список активних імен і при зустрічі з директивою #if NAME перевіряє, чи є NAME у цьому списку. Якщо імені немає — код всередині блоку ігнорується та не компілюється.

За даними Swift.org Blog (2025), використання Active Compilation Conditions замість прапорців часу виконання скорочує розмір release-бінарника в середньому на 12-18% для проєктів із розвиненою системою логування та налагоджувальних інструментів. Це особливо критично для мобільних застосунків з обмеженнями за розміром інсталяційного файлу.

Основна відмінність від умовної компіляції на рівні препроцесора C/C++ — Active Compilation Conditions у Swift та Kotlin працюють на рівні компілятора AST (Abstract Syntax Tree), а не на рівні текстової заміни. Це робить їх безпечнішими та передбачуванішими: будь-яка синтаксична помилка в неактивній гілці #if буде виявлена на етапі парсингу, а не проявиться під час виконання.

Ще одна важлива відмінність — у Swift умова #if os(iOS) || os(macOS) перевіряється на етапі компіляції та працює з іменами платформ, а не з макросами препроцесора. Це виключає цілий клас багів, пов'язаних із неправильним вставленням тексту через #define, які можливі в препроцесорі C/C++. Компілятор Swift бачить AST, а не замінений текст, що робить налагодження умовної компіляції значно простішим.

Active Compilation Conditions у Swift

Swift підтримує Active Compilation Conditions через директиву #if, яка приймає список імен прапорців, об'єднаних логічними операторами &&, || та !. Компілятор включає код всередині #if ... #endif лише якщо умова істинна.

Вбудовані умови Swift

Swift надає кілька вбудованих умов: DEBUG (автоматично активний у debug-збірці), swift(>=5.0) (перевірка версії компілятора), canImport(UIKit) (перевірка доступності модуля) та targetEnvironment(simulator) (перевірка середовища). Ці умови не потребують додаткового налаштування.

swift
// Вбудовані умови Swift
#if DEBUG
    print("Налагоджувальна збірка — логування активне")
#endif

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

Користувацькі прапорці в Xcode

Розробник може додавати свої прапорці через Build Setting OTHER_SWIFT_FLAGS у Xcode. Прапорець вказується з префіксом -D, наприклад -DBETA або -DANALYTICS_ENABLED. Для різних конфігурацій (Debug, Release, Staging) можна встановити різні набори прапорців.

swift
// Обробка свого прапорця BETA
#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
}

Платформені умови #if os()

Swift підтримує платформені умови: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Ці умови перевіряють цільову платформу збірки та дозволяють писати код, спільний для кількох платформ Apple, із платформо-специфічними блоками.

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 та Kotlin

В екосистемі Android Active Compilation Conditions реалізовані через систему BuildConfig, productFlavors та прапорці в build.gradle.kts. Kotlin не має прямого аналога директиви #if на рівні мови, але надає альтернативні механізми.

Поля BuildConfig як прапорці

Найпоширеніший спосіб — додати buildConfigField для кожного прапорця: buildConfigField("boolean", "BETA", "true"). Ці поля генеруються в клас BuildConfig для кожного Build Variant окремо. Поле DEBUG уже вбудоване та автоматично true для debug-збірок.

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

// Використання в коді Kotlin
if (BuildConfig.BETA) {
    enableBetaFeatures()
}

Source Sets та productFlavors

Gradle дозволяє створювати окремі директорії sourceSets для кожного флейвору. Наприклад, src/demo/ та src/full/. Класи з однаковим іменем у різних sourceSets замінюють один одного при збірці відповідного флейвору. Це потужніший механізм, ніж прапорці, тому що можна перевизначати цілі класи.

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
}

Для Kotlin Multiplatform (KMP) доступна директива expect/actual, яка дозволяє оголошувати очікувані оголошення у спільному коді та надавати платформені реалізації. Це механізм рівня компіляції, аналогічний Active Compilation Conditions за ефектом — неактивний код не компілюється для непідходящих платформ.

Сценарії використання та найкращі практики

Active Compilation Conditions застосовуються в чотирьох основних сценаріях: налагодження (логи, інспектори), A/B тестування (прапорці функцій), платформена адаптація (iOS/macOS спільний код) та ліцензування (безкоштовна/платна версії).

Налагоджувальне логування

Найчастіший сценарій — умовне логування. У debug-збірці всі логи пишуться в консоль, у release — нічого. Використання #if DEBUG або BuildConfig.DEBUG гарантує, що release-бінарник не містить жодного виклику логера, навіть заінлайненного.

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

Feature Flags на етапі збірки

Якщо нова функціональність ще не готова для продакшену, але вже є в коді, її можна приховати за прапорцем компіляції. На відміну від runtime feature flags, прапорці компіляції не навантажують застосунок перевірками та не можуть бути увімкнені користувачем.

  • Нові фічі — приховати незавершений функціонал до наступного релізу без видалення коду
  • Аналітика — підключити додатковий збір метрик лише для beta-тестерів
  • Сторонні SDK — виключити важкі бібліотеки з безкоштовної версії застосунку
  • UI компоненти — показувати експериментальні екрани лише в staging-збірці

Найкращі практики

Active Compilation Conditions слід використовувати помірно. Надмірна кількість прапорців робить код важким для розуміння: розробник не може бути впевненим, які гілки скомпілюються в даний момент. Рекомендується документувати кожен прапорець у README або спеціальному файлі CONFIG.md.

У великих проєктах із розподіленою командою корисно впровадити автоматичну валідацію прапорців у CI. Кожен pull request має проходити збірку з усіма можливими комбінаціями Active Compilation Conditions. Це гарантує, що код під неактивним прапорцем не зламався через рефакторинг, і жодна гілка умовної компіляції не залишилася неперевіреною до моменту релізу. Інструменти на кшталт xcresulttool (для iOS) та Gradle Build Scan (для Android) допомагають автоматизувати цей процес.

  • Мінімум прапорців — не більше 5-7 активних умов на проєкт. Кожен прапорець — точка складності.
  • Угода про імена — усі прапорці у ВЕЛИКИХ_ЛІТЕРАХ, із префіксом проєкту: MYAPP_BETA, MYAPP_ANALYTICS.
  • Code review — кожне додавання #if або buildConfigField має проходити окреме рев'ю.
  • Тестування — CI має збирати всі можливі комбінації прапорців хоча б раз на день.

Часті запитання

У чому різниця між #if DEBUG та if (isDebug) у Swift?

#if DEBUG — це директива компіляції: якщо DEBUG не активний, код всередині блоку не потрапляє в бінарник. if (isDebug) — перевірка під час виконання: код компілюється завжди, умова перевіряється під час виконання. #if не залишає слідів у release-збірці.

Як додати свій прапорець у Xcode?

У Build Settings проєкту знайти Other Swift Flags (OTHER_SWIFT_FLAGS) і додати новий рядок із прапорцем: -DMY_FLAG. Прапорець буде видимий директиві #if MY_FLAG. Можна встановити різні прапорці для Debug та Release конфігурацій.

Чи є в Kotlin аналог Swift #if?

У Kotlin/JVM немає прямого аналога. Натомість використовуються поля BuildConfig (перевірка під час виконання, але ProGuard може викинути невикористовуваний код). У Kotlin Multiplatform — директива expect/actual на рівні оголошень.

Чи можна комбінувати кілька прапорців в одному #if?

Так, Swift підтримує логічні оператори: #if DEBUG && BETA, #if os(iOS) || os(tvOS), #if !RELEASE. Умови можна групувати дужками для складної логіки. AND та OR працюють за стандартними правилами короткого замикання.

Чому #if DEBUG не працює в SwiftUI прев'ю?

SwiftUI прев'ю збираються в окремому процесі з прапорцями, відмінними від основного таргета. DEBUG може бути не активний. Рішення: використовувати targetEnvironment(simulator) для коду прев'ю або виносити умовну логіку в окремі методи.

Підсумки

  • Active Compilation Conditions — прапорці компіляції, які фізично виключають неактивний код із бінарного файлу без перевірок під час виконання.
  • Swift підтримує #if із вбудованими умовами (DEBUG, os, canImport) та користувацькими прапорцями через OTHER_SWIFT_FLAGS.
  • Android та Kotlin використовують поля BuildConfig, productFlavors та механізм expect/actual у KMP.
  • Продуктивність — умовна компіляція зменшує розмір бінарника на 12-18% у проєктах із розвиненою системою логування.
  • Feature flags на етапі компіляції не навантажують застосунок перевірками та не можуть бути увімкнені користувачем.
  • Рекомендації — не більше 5-7 прапорців на проєкт, угода про імена з префіксом, обов'язкове тестування всіх комбінацій у CI.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також