#ifdef замена в языке Swift

734

В C / C ++ / Objective C вы можете определить макрос, используя препроцессоры компилятора. Кроме того, вы можете включать / исключать некоторые части кода, используя препроцессоры компилятора.

#ifdef DEBUG
    // Debug-only code
#endif

Есть ли подобное решение в Swift?

MXG
источник
1
Как идея, вы могли бы поместить это в ваши заголовки моста obj-c ..
Matej
42
Вы действительно должны присудить ответ, так как у вас есть несколько вариантов на выбор, и этот вопрос принес вам много голосов.
Дэвид Х

Ответы:

1069

Да, ты можешь сделать это.

В 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работает. Кажется, компилятор игнорирует флаг с определенным значением.

Жан Ле Муаньян
источник
41
Это правильный ответ, хотя следует отметить, что вы можете проверить только наличие флага, но не конкретное значение.
Чарльз Харли
19
Дополнительное примечание : Помимо добавления, -D DEBUGкак указано выше, вам также нужно определить DEBUG=1в Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Мэтью Кирос
38
Я не мог заставить это работать, пока я не изменил форматирование -DDEBUGс этого ответа: stackoverflow.com/a/24112024/747369 .
Крамер
11
@MattQuiros Там нет необходимости добавлять DEBUG=1к Preprocessor Macros, если вы не хотите использовать его в коде Objective-C.
дерполюк
7
@Daniel Вы можете использовать стандартные логические операторы (например: «#if! DEBUG»)
Жан Ле Мойнан,
353

Как указано в Apple Docs

Компилятор Swift не включает препроцессор. Вместо этого он использует преимущества атрибутов времени компиляции, конфигураций сборки и языковых функций для достижения той же функциональности. По этой причине директивы препроцессора не импортируются в Swift.

Мне удалось достичь того, чего я хотел, используя пользовательские конфигурации сборки:

  1. Зайдите в ваш проект / выберите цель / Настройки сборки / найдите пользовательские флаги
  2. Для выбранной цели установите свой пользовательский флаг, используя префикс -D (без пробелов), как для Debug, так и для Release
  3. Выполните вышеуказанные шаги для каждой цели

Вот как вы проверяете цель:

#if BANANA
    print("We have a banana")
#elseif MELONA
    print("Melona")
#else
    print("Kiwi")
#endif

введите описание изображения здесь

Протестировано с использованием Swift 2.2

Andrej
источник
4
1. также с пробелами, 2. установить флаг только для отладки?
c0ming
3
@ c0ming зависит от ваших потребностей, но если вы хотите, чтобы что-то происходило только в режиме отладки, а не в выпуске, вам нужно удалить -DDEBUG из Release.
cdf1982
1
После того, как я установил собственный флаг -DLOCAL, на моем #if LOCAl #else #endif, он попадает в #elseраздел. Я продублировал оригинальную цель AppTargetи переименовал ее в AppTargetLocal& установил ее собственный флаг.
Perwyl Liu
3
@ Андрей, ты случайно не знаешь, как заставить XCTest также распознавать пользовательские флаги? Я понимаю, что это #if LOCAL соответствует ожидаемому результату, когда я бегу с симулятором, и попадаю во #else время тестирования. Я хочу, чтобы это также попало во #if LOCALвремя тестирования.
Perwyl Liu
3
Это должен быть принятый ответ. Текущий принятый ответ неверен для Swift, поскольку он применим только к Objective-C.
miken.mkndev
171

Во многих ситуациях вам не нужна условная компиляция ; вам просто нужно условное поведение, которое вы можете включать и выключать. Для этого вы можете использовать переменную окружения. Это имеет огромное преимущество в том, что вам не нужно перекомпилировать.

Вы можете установить переменную окружения и легко включить или выключить ее в редакторе схем:

введите описание изображения здесь

Вы можете получить переменную среды с NSProcessInfo:

    let dic = NSProcessInfo.processInfo().environment
    if dic["TRIPLE"] != nil {
        // ... do secret stuff here ...
    }

Вот пример из жизни. Мое приложение работает только на устройстве, потому что оно использует музыкальную библиотеку, которой нет в симуляторе. Как же тогда делать снимки экрана на симуляторе для устройств, которыми я не владею? Без этих снимков экрана я не могу отправить в AppStore.

Мне нужны поддельные данные и другой способ их обработки . У меня есть две переменные окружения: одна, которая при включении говорит приложению генерировать поддельные данные из реальных данных во время работы на моем устройстве; другой, который при включении использует фальшивые данные (не отсутствующую музыкальную библиотеку) во время работы на симуляторе. Включить / отключить каждый из этих специальных режимов легко благодаря флажкам переменных среды в редакторе схем. И бонус в том, что я не могу случайно использовать их в моей сборке App Store, потому что архивация не имеет переменных среды.

матовый
источник
По какой-то причине моя переменная окружения вернулась как ноль при втором запуске приложения
Евгений
60
Обратите внимание : переменные среды устанавливаются для всех конфигураций сборки, их нельзя устанавливать для отдельных. Так что это не жизнеспособное решение, если вам нужно изменить поведение в зависимости от того, является ли это релизом или отладочной сборкой.
Эрик
5
@Eric Согласен, но они не установлены для всех действий схемы. Таким образом, вы можете сделать одну вещь в build-and-run и другую вещь в архиве, что часто является реальным отличием, которое вы хотите провести. Или вы можете иметь несколько схем, которые также являются обычной практикой в ​​реальной жизни. Кроме того, как я уже сказал в своем ответе, включение и отключение переменных среды в схеме очень просто.
Мэтт
10
Переменные среды НЕ работают в режиме архива. Они применяются только при запуске приложения из XCode. Если вы попытаетесь получить доступ к ним на устройстве, приложение упадет. Выяснил трудный путь.
iupchris10
2
@ iupchris10 «В архиве нет переменных среды» - последние слова моего ответа выше. Это, как я говорю в своем ответе, хорошо . Это главное .
Мэтт
161

Главное изменение ifdefзамены пришло с Xcode 8. то есть использование Активных Условий Компиляции .

См. Построение и Связывание в Примечании к выпуску Xcode 8 .

Новые настройки сборки

Новая настройка: SWIFT_ACTIVE_COMPILATION_CONDITIONS

Active Compilation Conditionsis 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/

DShah
источник
Есть ли способ отключить установленные условия активной компиляции во время сборки? Мне нужно отключить условие DEBUG при создании конфигурации отладки для тестирования.
Джонни
1
@Jonny Единственный способ, который я нашел, - создать третью конфигурацию сборки для проекта. На вкладке «Проект»> «Информация»> «Конфигурации» нажмите «+», затем дублируйте «Отладка». Затем вы можете настроить условия активной компиляции для этой конфигурации. Не забудьте отредактировать схему Target> Test, чтобы использовать новую конфигурацию сборки!
Матиас
1
Это должен быть правильный ответ ... это единственное, что работало для меня на xCode 9 с использованием Swift 4.x!
Шокавели
1
Кстати, в Xcode 9.3 Swift 4.1 DEBUG уже присутствует в условиях активной компиляции, и вам не нужно ничего добавлять для проверки конфигурации DEBUG. Просто #if DEBUG и #endif.
Денис Кутлубаев
Я думаю, что это не по теме, и это плохо. Вы не хотите отключать условия активной компиляции. вам нужна новая и другая конфигурация для тестирования - на ней НЕ будет тега "Debug". Узнайте о схемах.
Мотти Шнеор
93

Начиная с Swift 4.1, если все, что вам нужно, это просто проверить, собран ли код с конфигурацией отладки или выпуска, вы можете использовать встроенные функции:

  • _isDebugAssertConfiguration()(верно, когда оптимизация установлена ​​в -Onone)
  • _isReleaseAssertConfiguration()(верно, когда оптимизация установлена ​​в -O) (недоступно в Swift 3+)
  • _isFastAssertConfiguration()(верно, когда оптимизация установлена ​​в -Ounchecked)

например

func obtain() -> AbstractThing {
    if _isDebugAssertConfiguration() {
        return DecoratedThingWithDebugInformation(Thing())
    } else {
        return Thing()
    }
}

По сравнению с макросами препроцессора,

  • ✓ Вам не нужно определять обычай -D DEBUG флаг, чтобы использовать его
  • ~ На самом деле это определяется настройками оптимизации, а не конфигурацией сборки XCode
  • Ocu Недокументированное, что означает, что функция может быть удалена при любом обновлении (но она должна быть безопасной для AppStore, так как оптимизатор превратит их в константы)

  • In Использование в if / else всегда будет генерировать предупреждение «никогда не будет выполнено».

kennytm
источник
1
Эти встроенные функции оцениваются во время компиляции или во время выполнения?
ma11hew28
@MattDiPasquale Время оптимизации. if _isDebugAssertConfiguration()будет оцениваться if falseв режиме выпуска и в режиме if trueотладки.
Kennytm
2
Я не могу использовать эти функции, чтобы отказаться от какой-либо отладочной переменной в релизе.
Франклин Ю.
3
Эти функции где-то задокументированы?
Том Харрингтон
7
Начиная с Swift 3.0 и XCode 8, эти функции недействительны.
CodeBender
87

Xcode 8 и выше

Используйте параметр « Условия активной компиляции» в « Параметры сборки» / «Компилятор Swift» - пользовательские флаги .

  • Это новый параметр сборки для передачи флагов условной компиляции компилятору Swift.
  • Просто добавьте флаги, как это: ALPHAи BETAт. Д.

Затем проверьте это с условиями компиляции, как это:

#if ALPHA
    //
#elseif BETA
    //
#else
    //
#endif

Совет: вы также можете использовать #if !ALPHAи т. Д.

Якуб Трухларж
источник
77

Нет препроцессора Swift. (С одной стороны, произвольная замена кода нарушает безопасность типов и памяти.)

Swift действительно включает параметры конфигурации во время сборки, поэтому вы можете условно включать код для определенных платформ или стилей сборки или в ответ на флаги, которые вы определяете с помощью -Dаргументов компилятора. В отличие от C, условно скомпилированный раздел вашего кода должен быть синтаксически завершен. Об этом есть раздел « Использование Swift с какао и Objective-C» .

Например:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
rickster
источник
34
«С одной стороны, произвольная замена кода нарушает безопасность типов и памяти». Разве препроцессор не выполняет свою работу раньше, чем это делает компилятор (отсюда и название)? Таким образом, все эти проверки все еще могут иметь место.
Тило
10
@ Thilo Я думаю, что это ломает поддержку IDE
Александр Дубинский
1
Я думаю, что @rickster делает то, что макросы препроцессора C не имеют представления о типе, и их наличие нарушит требования к типу Swift. Причина, по которой макросы работают в C, заключается в том, что C допускает неявное преобразование типов, а это значит, что вы можете поместить INT_CONSTкуда угодно a float. Свифт не допустит этого. Кроме того, если бы вы могли сделать это var floatVal = INT_CONSTнеизбежно, это могло бы произойти сбой где-то позже, когда компилятор ожидает, Intно вы используете его как Float(тип floatValбудет выведен как Int). 10 кастов спустя, и это просто чище, чтобы удалить макросы ...
Ephemera
Я пытаюсь использовать это, но это, похоже, не работает, он все еще компилирует код Mac в сборках iOS. Где-то еще есть экран настройки, который нужно настроить?
Мори Марковиц
1
@ Таким образом, вы правы - препроцессор не нарушает безопасность типов и памяти.
tcurdt
50

Мои два цента для Xcode 8:

а) Пользовательский флаг с использованием -D префикса работает нормально, но ...

б) более простое использование:

В Xcode 8 есть новый раздел: «Условия активной компиляции», уже с двумя строками, для отладки и выпуска.

Просто добавьте свое определение БЕЗ -D.

ingconti
источник
Спасибо за упоминание о том, что есть две строки для отладки и выпуска
Ицхак
кто-нибудь проверял это в релизе?
Гленн
Это обновленный ответ для быстрых пользователей. т.е. без -D.
Мани
46

Константа 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 не будет генерировать предупреждение «никогда не будет выполнено».

Джон Уиллис
источник
25

Ответ Moignans здесь отлично работает. Вот еще одна информация, если это поможет,

#if DEBUG
    let a = 2
#else
    let a = 3
#endif

Вы можете отменить макросы, как показано ниже,

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif
Саззад Хисейн Хан
источник
23

В проектах 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 напишет более подробное содержание и индексы для своих книг.

Вадим Моторин
источник
17

XCODE 9 И ВЫШЕ

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
мидхун р
источник
4
вау это самое уродливое сокращение, которое я когда-либо видел: p
rmp251
7

После настройки 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

Это то, что компилятор проверяет синтаксис моего кода, поэтому я уверен, что его синтаксис правильный и строит.

Rivera
источник
3

! [В Xcode 8 и выше перейдите к настройке сборки -> поиск пользовательских флагов] 1

В коде

 #if Live
    print("Live")
    #else
    print("debug")
    #endif
sachin_kvk
источник
Вы попали на это здесь! Swift #if просматривает пользовательские флаги НЕ макросы препроцессора. Пожалуйста, обновите ваш ответ содержанием по ссылке, часто ссылки через некоторое время ломаются
Дейл
3
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

Источник

Адам Смака
источник
1
Это не условная компиляция. Хотя это полезно, это просто старая условная среда выполнения. ОП спрашивает после компиляции для целей метапрограммирования
Шейн
3
Просто добавьте @inlinableперед, funcи это будет самый элегантный и идиоматический способ для Swift. В сборках релизов ваш code()блок будет оптимизирован и полностью исключен. Аналогичная функция используется в собственной платформе NIO от Apple.
Моджуба
1

Это основывается на ответе Джона Уиллиса , основанном на утверждении, которое выполняется только в отладочных компиляциях:

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 полностью исключает вызов функции.

Уоррен Стрингер
источник
Исключает, так как in удаляет вызов полностью, когда не в отладке - из-за пустой функции? Это было бы замечательно.
Йохан