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

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

#ifdef DEBUG
    // Debug-only code
#endif

Есть ли аналогичное решение в Swift?

Ответ 1

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

В Swift вы все равно можете использовать макросы препроцессора "# if/# else/# endif" (хотя и более ограниченные), согласно Apple docs. Вот пример:

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

Теперь вы должны установить символ "DEBUG" в другом месте. Установите его в разделе "Swift Compiler - Custom Flags", "Other Swift Flags". Вы добавляете символ DEBUG с записью -D DEBUG.

Как обычно, вы можете установить другое значение в Debug или в Release.

Я тестировал его в реальном коде, и он работает; он, по-видимому, не распознается на игровой площадке.

Вы можете прочитать мой оригинальный пост here.


ВАЖНОЕ ПРИМЕЧАНИЕ: -DDEBUG=1 не работает. Работает только -D DEBUG. Кажется, компилятор игнорирует флаг с определенным значением.

Ответ 2

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

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

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

  • Перейдите в свой проект/выберите цель/Настройки сборки/поиск пользовательских флагов
  • Для выбранной вами цели установите свой собственный флаг с использованием префикса -D (без пробелов), как для отладки, так и для выпуска
  • Выполняйте шаги выше для каждой вашей цели.

Здесь вы проверяете цель:

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

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

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

Ответ 3

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

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

enter image description here

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

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

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

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

Ответ 4

Основное изменение замены ifdef вызвало Xcode 8. Использование Активных условий компиляции.

Обратитесь к Создание и привязка в Xcode 8 Release note.

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

Новая настройка: 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 в новый параметр. Время для перемещения всех этих условных значений компиляции!

Обратитесь к ссылке ниже для более быстрой настройки параметров настройки в Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

Ответ 5

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

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

например

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

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

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

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

Ответ 6

Xcode 8 и выше

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

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

Затем проверьте его с помощью условий компиляции следующим образом:

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

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

Ответ 7

Нет препроцессора Swift. (Во-первых, произвольная замена кода прерывает безопасность типов и памяти.)

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

Например:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif

Ответ 8

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

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

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

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

Просто добавьте свой параметр WITHOUT -D.

Ответ 9

Константа isDebug в зависимости от условий активной компиляции

Другое, возможно, более простое, решение, которое все еще приводит к логическому значению, которое вы можете передавать в функции без #if условных обозначений #if всей вашей кодовой базе, состоит в том, чтобы определить DEBUG как одно из целевых Active Compilation Conditions вашего проекта и включить следующее (я определяю его как глобальная постоянная):

#if DEBUG
    let isDebug = true
#else
    let isDebug = false
#endif

Константа isDebug на основе настроек оптимизации компилятора

Эта концепция основывается на ответе kennytm

Основное преимущество по сравнению с 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 не будет генерировать предупреждение "никогда не будет выполнено".

Ответ 10

В проектах Swift, созданных с версией Xcode 9.4.1, Swift 4.1

#if DEBUG
#endif

работает по умолчанию, потому что в макросах препроцессора DEBUG = 1 уже был установлен Xcode.

Так что вы можете использовать #if DEBUG "из коробки".

Между прочим, как использовать блоки компиляции условий в общем, написано в книге Apple "Язык программирования Swift 4.1" (раздел "Операции управления компилятором"), а как написать флаги компиляции и что является аналогом макросов C в Swift, написано в другая книга Apple, использующая Swift с cocoa и Objective C (в разделе Директивы препроцессора)

Надеюсь, что в будущем Apple напишет более подробное содержание и индексы для своих книг.

Ответ 11

XCODE 9 И ВЫШЕ

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif

Ответ 12

После установки DEBUG=1 в ваших GCC_PREPROCESSOR_DEFINITIONS сборки 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

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

Ответ 14

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

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

Ответ 15

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

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

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

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif

Ответ 16

func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

Источник