#ifdef замена в языке Swift
В C/C++/Objective-C вы можете определить макрос, используя препроцессоры компилятора. Более того, вы можете включать / исключать некоторые части кода, используя препроцессоры компилятора.
#ifdef DEBUG
// Debug-only code
#endif
Есть ли подобное решение в Swift?
19 ответов
Да, ты можешь сделать это.
В Swift вы все еще можете использовать макросы препроцессора "#if/#else/#endif" (хотя и более ограниченные), как указано в документации Apple. Вот пример:
#if DEBUG
let a = 2
#else
let a = 3
#endif
Теперь вы должны установить символ "DEBUG" в другом месте. Установите его в разделе "Swift Compiler - Custom Flags", строка "Другие флаги Swift". Вы добавляете символ DEBUG с помощью -D DEBUG
запись.
Как обычно, вы можете установить другое значение в Debug или в Release.
Я проверил это в реальном коде, и он работает; это, кажется, не признается на детской площадке, хотя.
Вы можете прочитать мой оригинальный пост здесь.
ВАЖНАЯ ЗАМЕТКА: -DDEBUG=1
не работает Только -D DEBUG
работает. Кажется, компилятор игнорирует флаг с определенным значением.
Как указано в Apple Docs
Компилятор Swift не включает препроцессор. Вместо этого он использует преимущества атрибутов времени компиляции, конфигураций сборки и языковых функций для достижения той же функциональности. По этой причине директивы препроцессора не импортируются в Swift.
Мне удалось достичь того, что я хотел, используя пользовательские конфигурации сборки:
- Зайдите в свой проект / выберите цель / Настройки сборки / найдите пользовательские флаги
- Для выбранной цели установите свой пользовательский флаг, используя префикс -D (без пробелов), как для Debug, так и для Release
- Выполните вышеуказанные шаги для каждой цели
Вот как вы проверяете цель:
#if BANANA
print("We have a banana")
#elseif MELONA
print("Melona")
#else
print("Kiwi")
#endif
Протестировано с использованием Swift 2.2
Серьезное изменение ifdef
замена пришла с Xcode 8. т.е. использование условий активной компиляции.
См. Построение и Связывание в Примечании к выпуску Xcode 8.
Новые настройки сборки
Новая настройка: SWIFT_ACTIVE_COMPILATION_CONDITIONS
“Active Compilation Conditions” is a new build setting for passing conditional compilation flags to the Swift compiler.
Ранее нам приходилось объявлять ваши флаги условной компиляции в OTHER_SWIFT_FLAGS, не забывая добавлять "-D" к настройке. Например, для условной компиляции со значением MYFLAG:
#if MYFLAG1
// stuff 1
#elseif MYFLAG2
// stuff 2
#else
// stuff 3
#endif
Значение, добавляемое к настройке -DMYFLAG
Теперь нам нужно только передать значение MYFLAG в новую настройку. Время переместить все эти условные значения компиляции!
Пожалуйста, обратитесь к ссылке ниже для получения дополнительной функции Настройки сборки Swift в Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/
Во многих ситуациях вам не нужна условная компиляция; вам просто нужно условное поведение, которое вы можете включать и выключать. Для этого вы можете использовать переменную окружения. Это имеет огромное преимущество в том, что вам не нужно перекомпилировать.
Вы можете установить переменную окружения и легко включить или выключить ее в редакторе схем:
Вы можете получить переменную среды с помощью NSProcessInfo:
let dic = NSProcessInfo.processInfo().environment
if dic["TRIPLE"] != nil {
// ... do secret stuff here ...
}
Вот пример из жизни. Мое приложение работает только на устройстве, потому что оно использует музыкальную библиотеку, которой нет в Симуляторе. Как сделать снимки экрана на симуляторе для устройств, которыми я не владею? Без этих снимков экрана я не могу отправить в AppStore.
Мне нужны поддельные данные и другой способ их обработки. У меня есть две переменные окружения: одна, которая при включении говорит приложению генерировать поддельные данные из реальных данных во время работы на моем устройстве; другой, который при включении использует фальшивые данные (не отсутствующую музыкальную библиотеку) во время работы на симуляторе. Включить / выключить каждый из этих специальных режимов легко благодаря флажкам переменных среды в редакторе схем. И бонус в том, что я не могу случайно использовать их в моей сборке App Store, потому что архивация не имеет переменных среды.
Начиная с Swift 4.1, если все, что вам нужно, это просто проверить, собран ли код с конфигурацией отладки или выпуска, вы можете использовать встроенные функции:
_isDebugAssertConfiguration()
(верно, когда оптимизация установлена на-Onone
)(недоступно в Swift 3+)_isReleaseAssertConfiguration()
(верно, когда оптимизация установлена на-O
)_isFastAssertConfiguration()
(верно, когда оптимизация установлена на-Ounchecked
)
например
func obtain() -> AbstractThing {
if _isDebugAssertConfiguration() {
return DecoratedThingWithDebugInformation(Thing())
} else {
return Thing()
}
}
По сравнению с макросами препроцессора,
- ✓ Вам не нужно определять обычай
-D DEBUG
флаг, чтобы использовать его - ~ Это на самом деле определяется с точки зрения настроек оптимизации, а не конфигурации сборки XCode
Ocu Недокументированное, что означает, что функция может быть удалена при любом обновлении (но она должна быть безопасной для AppStore, так как оптимизатор превратит их в константы)
- когда-то они были удалены, но возвращены публике из-за отсутствия
@testable
атрибут, судьба неясна для будущего Swift.
- когда-то они были удалены, но возвращены публике из-за отсутствия
In Использование в if/else всегда будет генерировать предупреждение "никогда не будет выполнено".
Xcode 8 и выше
Используйте параметр " Условия активной компиляции" в " Параметры сборки" / "Компилятор Swift" - пользовательские флаги.
- Это новый параметр сборки для передачи флагов условной компиляции компилятору Swift.
- Просто добавьте флаги, как это:
ALPHA
,BETA
и т.п.
Затем проверьте это с условиями компиляции, как это:
#if ALPHA
//
#elseif BETA
//
#else
//
#endif
Совет: вы также можете использовать
#if !ALPHA
и т.п.
Нет препроцессора Swift. (С одной стороны, произвольная замена кода нарушает безопасность типов и памяти.)
Swift действительно включает параметры конфигурации во время сборки, поэтому вы можете условно включать код для определенных платформ или стилей сборки или в ответ на флаги, которые вы определили с помощью -D
аргументы компилятора. В отличие от C, условно скомпилированный раздел вашего кода должен быть синтаксически завершенным. Об этом есть раздел " Использование Swift с какао и Objective-C".
Например:
#if os(iOS)
let color = UIColor.redColor()
#else
let color = NSColor.redColor()
#endif
Константа isDebug в зависимости от условий активной компиляции
Другое, возможно, более простое, решение, которое все еще приводит к логическому значению, которое вы можете передавать в функции без перетаскивания. #if
условные выражения по всей вашей кодовой базе, чтобы определить DEBUG
как одна из ваших целей сборки проекта Active Compilation Conditions
и включить следующее (я определяю это как глобальную константу):
#if DEBUG
let isDebug = true
#else
let isDebug = false
#endif
Константа isDebug на основе настроек оптимизации компилятора
Эта концепция основана на ответе Кеннимма
Основное преимущество по сравнению с Kennytm заключается в том, что это не зависит от частных или недокументированных методов.
В Swift 4:
let isDebug: Bool = {
var isDebug = false
// function with a side effect and Bool return value that we can pass into assert()
func set(debug: Bool) -> Bool {
isDebug = debug
return isDebug
}
// assert:
// "Condition is only evaluated in playgrounds and -Onone builds."
// so isDebug is never changed to true in Release builds
assert(set(debug: true))
return isDebug
}()
По сравнению с макросами препроцессора и ответом kennytm,
- ✓ Вам не нужно определять обычай
-D DEBUG
флаг, чтобы использовать его - ~ Это на самом деле определяется с точки зрения настроек оптимизации, а не конфигурации сборки XCode
✓ Документировано, что означает, что функция будет следовать нормальным шаблонам выпуска / устаревания API.
✓ Использование в if/else не будет генерировать предупреждение "никогда не будет выполнено".
Мои два цента для Xcode 8:
а) Пользовательский флаг с использованием -D
префикс работает нормально, но...
б) более простое использование:
В Xcode 8 есть новый раздел: "Условия активной компиляции", уже с двумя строками, для отладки и выпуска.
Просто добавьте свое определение БЕЗ -D
,
Ответ Moignans здесь отлично работает. Вот еще одна информация, если это поможет,
#if DEBUG
let a = 2
#else
let a = 3
#endif
Вы можете отменить макросы, как показано ниже,
#if !RELEASE
let a = 2
#else
let a = 3
#endif
В проектах Swift, созданных с версией Xcode 9.4.1, Swift 4.1
#if DEBUG
#endif
работает по умолчанию, потому что в макросах препроцессора DEBUG=1 уже был установлен Xcode.
Так что вы можете использовать #if DEBUG "из коробки".
Между прочим, как использовать блоки компиляции условий в общем, написано в книге Apple "Язык программирования Swift 4.1" (раздел "Операции управления компилятором"), а как написать флаги компиляции и то, что является аналогом макросов C в Swift, написано в другая книга Apple, использующая Swift с какао и Objective C (в разделе Директивы препроцессора)
Надеюсь, что в будущем Apple напишет более подробное содержание и индексы для своих книг.
Есть несколько процессоров, которые возражают, и я перечислил их ниже. вы можете изменить аргумент по своему усмотрению:
#if os(macOS) /* Checks the target operating system */
#if canImport(UIKit) /* Check if a module presents */
#if swift(<5) /* Check the Swift version */
#if targetEnvironment(simulator) /* Check envrionments like Simulator or Catalyst */
#if compiler(<7) /* Check compiler version */
Также вы можете использовать любые настраиваемые флаги, например DEBUG
или любые другие флаги, которые вы определили
#if DEBUG
print("Debug mode")
#endif
XCODE 9 И ВЫШЕ
#if DEVELOP
//
#elseif PRODCTN
//
#else
//
#endif
После настройки DEBUG=1
в вашем GCC_PREPROCESSOR_DEFINITIONS
Настройки сборки Я предпочитаю использовать функцию для выполнения этих вызовов:
func executeInProduction(_ block: () -> Void)
{
#if !DEBUG
block()
#endif
}
А затем просто включите в эту функцию любой блок, который я хочу опустить в сборках Debug:
executeInProduction {
Fabric.with([Crashlytics.self]) // Compiler checks this line even in Debug
}
Преимущество по сравнению с:
#if !DEBUG
Fabric.with([Crashlytics.self]) // This is not checked, may not compile in non-Debug builds
#endif
Это то, что компилятор проверяет синтаксис моего кода, поэтому я уверен, что его синтаксис правильный и строит.
![В Xcode 8 и выше перейдите к настройке сборки -> поиск пользовательских флагов] 1
В коде
#if Live
print("Live")
#else
print("debug")
#endif
func inDebugBuilds(_ code: () -> Void) {
assert({ code(); return true }())
}
Это основано на ответе Джона Уиллиса, основанном на утверждении, которое выполняется только в отладочных компиляциях:
func Log(_ str: String) {
assert(DebugLog(str))
}
func DebugLog(_ str: String) -> Bool {
print(str)
return true
}
Мой вариант использования для регистрации операторов печати. Вот эталон для релизной версии на iPhone X:
let iterations = 100_000_000
let time1 = CFAbsoluteTimeGetCurrent()
for i in 0 ..< iterations {
Log ("⧉ unarchiveArray:\(fileName) memoryTime:\(memoryTime) count:\(array.count)")
}
var time2 = CFAbsoluteTimeGetCurrent()
print ("Log: \(time2-time1)" )
печатает:
Log: 0.0
Похоже, Swift 4 полностью исключает вызов функции.
Вы можете создать подобное перечисление с именем AppConfiguration, чтобы обеспечить безопасность типа необработанных значений.
enum AppConfiguration: String {
case debug
case release
}
поместите это в статическую переменную
struct Constants {
static var appConfiguration: AppConfiguration {
#if DEBUG
return .debug
#else
return .release
#endif
}
}
который вы затем можете использовать во всем своем проекте следующим образом:
if Constants.appConfiguration == .debug {
print("debug")
} else {
print("release")
}
Обновление Swift 5 для ответа мэтта
let dic = ProcessInfo.processInfo.environment
if dic["TRIPLE"] != nil {
// ... do your secret stuff here ...
}