«А где у вас #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)— версия языка SwiftcanImport(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 (активные условия компиляции).
Как настроить:
- Откройте проект в Xcode
- Выберите target → Build Settings → Swift Compiler — Custom Flags
- Найдите поле Active Compilation Conditions
- Добавьте свои флаги через пробел, например:
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 — используйте их. Условная компиляция хороша для платформенных различий и отладки, но плоха для бизнес-логики.