#ifdef в Swift: как заменить условную компиляцию из C/C++

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

«А где у вас #ifdef?» — первый вопрос C-разработчика в Swift

Если вы когда-нибудь переходили с C, C++ или Objective-C на Swift, то наверняка задавались вопросом: «А как тут сделать условную компиляцию? Где мой любимый #ifdef?» Это один из самых популярных вопросов на Stack Overflow — и неспроста. В мире Swift нет препроцессора в привычном понимании, но это не значит, что вы не можете гибко управлять сборкой под разные платформы, конфигурации или отладочные режимы.

В этой статье мы разберём лучшие способы замены #ifdef в Swift: от простых директив до продвинутых схем сборки. Вы узнаете, как писать код, который работает и на iOS, и на macOS, как скрыть отладочную информацию в релизе и как не запутаться в платформенных флагах. Поехали!

Почему в Swift нет #ifdef (и это хорошо)

Препроцессор C — мощный, но опасный инструмент. Он работает до компилятора, не знает про типы и области видимости. В результате легко получить #define, который ломает всё вокруг. Разработчики Swift решили пойти другим путём: они встроили условную компиляцию прямо в язык, сделав её безопаснее и прозрачнее.

Вместо #ifdef вы используете #if, #elseif, #else и #endif — синтаксис похож, но работает на уровне компилятора Swift, а не препроцессора. Плюс добавили несколько встроенных флагов, о которых мы сейчас поговорим.

Подход 1: Классические директивы #if — ближайший родственник #ifdef

Самый прямой способ — использовать #if с предопределёнными условиями:

#if DEBUG
    print("Это отладочная сборка")
    // Здесь может быть логирование, дополнительные проверки
#else
    print("Это релизная сборка")
#endif

Вот список встроенных флагов, которые вы можете использовать:

  • DEBUG — автоматически определён для Debug-конфигурации (Xcode добавляет его сам)
  • os(iOS), os(macOS), os(tvOS), os(watchOS) — проверка платформы
  • arch(x86_64), arch(arm64), arch(i386), arch(arm) — архитектура процессора
  • swift(>=5.0) — версия языка Swift
  • canImport(UIKit) — проверка доступности модуля
  • targetEnvironment(simulator) — работа на симуляторе

Пример комбинированной проверки:

#if os(iOS) || os(tvOS)
    import UIKit
    typealias PlatformView = UIView
#elseif os(macOS)
    import AppKit
    typealias PlatformView = NSView
#else
    typealias PlatformView = Any
#endif

Этот код создаёт единый алиас для вью-класса, который работает на разных платформах. Очень удобно для кроссплатформенных библиотек!

Подход 2: Active Compilation Conditions — ваш собственный #ifdef

А что, если нужно добавить свой флаг, которого нет в стандартном наборе? Например, BETA_FEATURES или PAYMENT_INTEGRATION. Для этого в Xcode есть Active Compilation Conditions (активные условия компиляции).

Как настроить:

  1. Откройте проект в Xcode
  2. Выберите target → Build Settings → Swift Compiler — Custom Flags
  3. Найдите поле Active Compilation Conditions
  4. Добавьте свои флаги через пробел, например: BETA PAYMENT_V2

В коде используем так:

#if BETA
    print("Запущена бета-версия")
    showBetaUI()
#endif

#if PAYMENT_V2
    let paymentProcessor = NewPaymentProcessor()
#else
    let paymentProcessor = LegacyPaymentProcessor()
#endif

Это прямая замена #ifdef из C/C++! Вы сами решаете, какие флаги активны для Debug, Release или любой кастомной конфигурации.

Подход 3: #available — проверка версий ОС без препроцессора

В C/C++ для проверки версий ОС часто использовали макросы вроде #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED). В Swift для этого есть #available — он работает на этапе выполнения, но компилятор оптимизирует недоступный код.

if #available(iOS 15, *) {
    // Используем новые API iOS 15
    let interaction = UICalendarView()
} else {
    // Падаем на старые API
    let datePicker = UIDatePicker()
}

Обратите внимание: #available — это не директива препроцессора, а обычное условие. Компилятор Swift анализирует обе ветки и не выдаёт ошибку, если код содержит вызовы недоступных API (но только внутри #available).

Для проверки версии Swift или наличия модуля используйте #if swift(>=5.5) или #if canImport(Combine) — это уже директивы времени компиляции.

Подход 4: Флаги сборки через -D (для продвинутых)

Если вы используете не Xcode, а Swift Package Manager или командную строку, флаги задаются через -D:

swift build -Xswiftc "-D" -Xswiftc "BETA"

В коде это работает так же:

#if BETA
    print("Собрано с флагом BETA")
#endif

Этот подход особенно полезен для CI/CD, когда вы хотите включать/выключать фичи без правки Xcode-проекта.

Сравнение подходов: что выбрать?

Давайте сведём всё в таблицу (мысленно, но очень наглядно):

  • #if os/arch/swift/canImport — для кроссплатформенного кода, когда нужно отличить iOS от macOS или симулятор от устройства
  • Active Compilation Conditions — для своих фича-флагов (бета, экспериментальные модули, A/B тесты)
  • #available — для проверки версий ОС и использования новых API с fallback
  • Флаги -D — для скриптов, CI/CD и SPM-проектов

Важно: Не злоупотребляйте #if. Если можно решить задачу через протоколы, полиморфизм или dependency injection — используйте их. Условная компиляция хороша для платформенных различий и отладки, но плоха для бизнес-логики.

Блоги

Рекомендаций по книгам