#ifdef заміна мовою Swift


735

У C / C ++ / Objective C ви можете визначити макрос за допомогою препроцесорів компіляції. Більше того, ви можете включити / виключити деякі частини коду за допомогою препроцесорів компіляції.

#ifdef DEBUG
    // Debug-only code
#endif

Чи є подібне рішення у Свіфта?


1
Як ідею, ви можете помістити це у свої мост-заголовки obj-c ..
Matej

42
Ви дійсно повинні нагородити, оскільки у вас є кілька варіантів, і це питання набрало вам багато голосів.
Девід Н

Відповіді:


1069

Так, ви можете це зробити.

У Swift ви все ще можете використовувати макроси препроцесора "# if / # else / # endif" (хоча і більш обмежені), відповідно до документів Apple . Ось приклад:

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

Тепер ви повинні встановити символ "DEBUG" в іншому місці. Встановіть його в розділі "Швидкий компілятор - Спеціальні прапори", рядок "Інші прапорці". Ви додаєте символ DEBUG із -D DEBUGзаписом.

Як завжди, ви можете встановити інше значення, коли ви налагоджуєтесь або коли випускаєте.

Я перевірив його в реальному коді, і він працює; Але, схоже, його не розпізнають на дитячому майданчику.

Ви можете прочитати мою оригінальну публікацію тут .


ВАЖЛИВА ПРИМІТКА: -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

Компілятор Swift не включає препроцесора. Натомість для отримання тієї ж функціональності використовується переваги атрибутів часу компіляції, побудови конфігурацій та мовних функцій. З цієї причини директиви препроцесора не імпортуються в Swift.

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

  1. Перейдіть до свого проекту / виберіть ціль / Налаштування побудови / шукайте спеціальні прапори
  2. Для вибраної цілі встановіть власний прапор, використовуючи префікс -D (без пробілів), як для налагодження, так і для випуску
  3. Виконайте вище кроки для кожної вашої цілі

Ось як перевірити ціль:

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

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

Тестовано за допомогою Swift 2.2


4
1.і також працює пробіл, 2.встановити прапор лише для налагодження?
c0ming

3
@ c0ming це залежить від ваших потреб, але якщо ви хочете, щоб щось сталося лише в режимі налагодження, а не у випуску, вам потрібно видалити -DDEBUG з Release.
cdf1982

1
Після того, як я встановив спеціальний прапор -DLOCAL, на моєму #if LOCAl #else #endifвін потрапляє в #elseрозділ. Я дублював оригінальну ціль AppTargetі перейменував її на AppTargetLocal& встановлював її власний прапор.
Первіль Лю

3
@Andrej Ви знаєте, як XCTest розпізнавати власні прапори? Я усвідомлюю, що це підпадає під #if LOCAL очікуваний результат, коли я біжу з тренажером і потрапляє під #else час тестування. Я хочу, щоб він потрапляв #if LOCALі під час тестування.
Первіль Лю

3
Це має бути прийнятою відповіддю. Поточна прийнята відповідь для Swift невірна, оскільки стосується лише Objective-C.
miken.mkndev

171

У багатьох ситуаціях вам не потрібна умовна компіляція ; вам просто потрібна умовна поведінка, яку ви можете включати і вимикати. Для цього можна використовувати змінну середовища. Це має величезну перевагу, яку вам насправді не потрібно перекомпілювати.

Ви можете встановити змінну середовища та легко увімкнути або вимкнути її в редакторі схем:

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

Ви можете отримати змінну оточення за допомогою NSProcessInfo:

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

Ось приклад із реального життя. Мій додаток працює лише на пристрої, оскільки він використовує музичну бібліотеку, якої не існує в Simulator. Як же тоді робити знімки екрана на Симуляторі для пристроїв, якими я не володію? Без цих знімків екрана я не можу подати в AppStore.

Мені потрібні підроблені дані та інший спосіб їх обробки . У мене є дві змінні середовища: одна, яка при включенні повідомляє програмі генерувати підроблені дані з реальних даних під час роботи на моєму пристрої; інший, який при вмиканні використовує підроблені дані (не відсутня бібліотека музики) під час роботи на Simulator. Увімкнення / вимкнення кожного з цих спеціальних режимів легко завдяки прапорцям змінної середовища в редакторі схем. Бонус у тому, що я не можу випадково їх використати у своїй збірці App Store, оскільки в архіві немає змінних середовищ.


Чомусь мінлива середовище повернулася як нуль на другому запуску програми
Євген

60
Слідкуйте : Змінні середовища встановлюються для всіх конфігурацій збірки, їх не можна встановлювати для окремих. Тож це не є прийнятним рішенням, якщо вам потрібно змінити поведінку залежно від того, чи це випуск, чи збірка налагодження.
Ерік

5
@Eric Погодився, але вони не встановлені для всіх дій схеми. Таким чином, ви можете зробити одне на створенні та запуску, а інше - в архіві, що часто відрізняє реальне життя, яке ви хочете зробити. Або у вас можуть бути декілька схем, які також є загальною моделлю в реальному житті. Плюс до цього, як я вже говорив у своїй відповіді, увімкнення та вимкнення змінних середовища у схемі дуже просто.
мат

10
Змінні середовища НЕ працюють в режимі архіву. Вони застосовуються лише при запуску програми з XCode. Якщо ви спробуєте отримати доступ до них на пристрої, програма перестане працювати. З’ясував важкий шлях.
iupchris10

2
@ iupchris10 "В архіві немає змінних середовищ" - це останні слова моєї відповіді, наведені вище. Це, як я кажу у своїй відповіді, добре . Це справа .
мат

159

Основна зміна 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 Build у Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


Чи все-таки слід відключити набір активних умов компіляції під час збирання? Мені потрібно відключити стан DEBUG при побудові конфігурації налагодження для тестування.
Джоні

1
@Jonny Єдиний спосіб, який я знайшов, - це створити 3-й конфігурацію збірки для проекту. На вкладці Проект> Інформація> Конфігурації натисніть "+", а потім повторіть налагодження. Потім ви можете налаштувати активні умови компіляції для цього конфігурації. Не забудьте відредагувати ціль> Тестові схеми для використання нової конфігурації збірки!
Маттіас

1
Це має бути правильна відповідь ... це єдине, що працювало для мене на xCode 9 за допомогою Swift 4.x!
шокавели

1
BTW, у Xcode 9.3 Swift 4.1 DEBUG вже є в активних умовах компіляції, і вам не потрібно нічого додавати, щоб перевірити конфігурацію DEBUG. Просто #if DEBUG та #endif.
Денис Кутлубаєв

Я думаю, що це і поза тематикою, і погано робити. ви не хочете відключати активні умови компіляції. вам потрібна нова і інша конфігурація для тестування - на якій НЕ буде тег "Налагодження". Дізнайтеся про схеми.
Мотті Шноор

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
  • Ndo Недокументований, що означає, що функцію можна видалити в будь-якому оновленні (але це має бути безпечно для AppStore, оскільки оптимізатор перетворить їх у константи)

  • ✗ Використання параметра if / else завжди генерує попередження "Ніколи не буде виконано".


1
Чи оцінюються ці вбудовані функції під час компіляції або під час виконання?
ma11hew28

@MattDiPasquale Оптимізація часу. if _isDebugAssertConfiguration()буде оцінено if falseв режимі випуску і if trueбуде в режимі налагодження.
kennytm

2
Я не можу використовувати ці функції для вимкнення певної змінної, що відповідає лише налагодженням у випуску.
Франклін Ю

3
Чи десь такі функції задокументовані?
Том Харрінгтон

7
Станом на Swift 3.0 та XCode 8 ці функції недійсні.
CodeBender

86

Xcode 8 і вище

Використовуйте налаштування Активні умови компіляції в налаштуваннях збірки / Swift компілятор - Спеціальні прапори .

  • Це нова настройка збірки для передачі умовних прапорів компіляції до компілятора Swift.
  • Прості оних прапори , як це: ALPHA, і BETAт.д.

Потім перевірте це з такими умовами компіляції :

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

Порада: Ви також можете використовувати #if !ALPHAі т.д.


77

Препроцесора Swift немає. (По-перше, довільна заміна коду порушує безпеку типу та пам'яті.)

Swift включає варіанти конфігурації часу побудови, тому ви можете умовно включати код для певних платформ або стилів збирання або у відповідь на прапори, які ви визначаєте за допомогою -Dаргументів компілятора. Однак, на відміну від C, умовно складений розділ вашого коду повинен бути синтаксично повним. Про це в розділі Використання Swift With Cocoa та Objective-C є розділ про це .

Наприклад:

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

34
"З одного боку, довільна заміна коду порушує безпеку типу та пам'яті." Чи не попередній процесор виконує свою роботу до того, як компілятор зробить (звідси назва)? Тож усі ці перевірки все-таки могли відбуватися.
Тіло

10
@Thilo Я думаю, що це порушує підтримку IDE
Олександр Дубінський,

1
Я думаю, що до @rickster досягається те, що макроси C Preprocessor не мають розуміння типу і їх наявність може порушити вимоги типу Swift. Причина роботи макросів у C полягає в тому, що C дозволяє неявну конверсію типу, а це означає, що ви можете розмістити своє місце, INT_CONSTде а floatбуде прийнято. Свіфт не дозволив цього зробити. Крім того, якщо ви могли б зробити var floatVal = INT_CONSTце неминуче, він би розпався десь пізніше, коли компілятор очікує, Intале ви використовуєте його як Float(тип floatValбуде зроблено як Int). 10 лит пізніше, а його просто очищувач для видалення макросів ...
Ефемера,

Я намагаюся використовувати це, але це, здається, не працює, він все ще компілює код Mac для iOS-версій. Чи є десь інший екран налаштування, який треба налаштувати?
Maury Markowitz

1
@Тило ви маєте рацію - попередній процесор не порушує безпеку будь-якого типу та пам'яті.
tcurdt

50

Мої два центи за Xcode 8:

а) Спеціальний прапор із використанням -Dпрефікса працює чудово, але ...

b) Простіше використання:

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

Просто додайте своє визначення БЕЗ -D.


Дякуємо за те, що ви сказали, що ДВІ РЕЧИ ДЛЯ ДЕБУГУ ТА ЗВ'ЯЗКУ
Іцчак

хтось тестував це у випуску?
Гленн

Це оновлена ​​відповідь для швидких користувачів. тобто без -D.
Мані

46

isDebug постійний на основі активних умов компіляції

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

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

isDebug Constant На основі параметрів оптимізації компілятора

Ця концепція ґрунтується на відповіді кеннітма

Основна перевага при порівнянні з кеннітмом полягає в тому, що це не покладається на приватні або недокументовані методи.

У 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

Відповідь Мойган тут чудово працює. Ось ще одна інформація про те, якщо це допоможе,

#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 The Swift Programming Language 4.1 (розділ Заяви управління компілятором) і як написати прапорці компіляції та про що пише аналог макросів C у Swift. ще одна книга Apple, що використовує Swift з какао та об'єктом C (у розділі Директиви щодо препроцесорів)

Сподіваюся, що в майбутньому Apple напише більш детальний зміст та покажчики своїх книг.



7

Після налаштування DEBUG=1в налаштуваннях GCC_PREPROCESSOR_DEFINITIONSзбірки я вважаю за краще використовувати функцію для здійснення цих дзвінків:

func executeInProduction(_ block: () -> Void)
{
    #if !DEBUG
        block()
    #endif
}

А потім просто додайте в цю функцію будь-який блок, який я хочу опустити в налагодженнях налагодження:

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

Це компілятор перевіряє синтаксис мого коду, тож я впевнений, що його синтаксис є правильним і будується.


3

Ви сюди потрапили! Swift #if дивиться на власні прапори НЕ препроцесорні макроси. Будь ласка, оновіть свою відповідь вмістом за посиланням, часто посилання через деякий час
Дейл

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

Джерело


1
Це не умовна компіляція. Хоча корисно, це просто звичайний старий час виконання. ОП просить після compiletime для цілей метапрограмування
Shayne

3
Просто додайте @inlinableперед funcцим, і це було б найвишуканішим та ідіоматичним способом для Свіфта. У версії версій ваш code()блок буде оптимізований та усунутий взагалі. Аналогічна функція використовується у власній системі NIO від Apple.
mojuba

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 повністю виключає виклик функції.


Виключає, як при видаленні виклику в повному обсязі, коли не в налагодженні - через те, що функція порожня? Це було б ідеально.
Йохан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.