Swift语言中的#ifdef替代方法

875

在C/C++/Objective C中,您可以使用编译器预处理器定义宏。 此外,您还可以使用编译器预处理器包含/排除一些代码部分。

#ifdef DEBUG
    // Debug-only code
#endif

在 Swift 中是否有类似的解决方案?


1
作为一个想法,你可以将这个放在你的 Obj-C 桥接头文件中。 - Matej
75
由于您有多个可供选择的答案,而且这个问题已经得到了很多赞,所以您真的应该选出一个最佳答案。 - David H
1
@Userthatisnotauser,你完全没有理解重点。你提问,你得到了很好的答案——选择一个。不要忽视时间和精力。 - David H
1
@DavidH 不,实际上是相反的。我的评论只是关于42的《银河系漫游指南》的参考。我完全同意,并想点赞,但我无法让自己成为第43个点赞者。 - ReinstateMonica3167040
2
@Userthatisnotauser 这位发帖者有19k积分 - 人们投票支持他的回答,但他似乎不关心那些帮助他的人。我总是会选择一个答案。 - David H
1
检查他们的账户,它已经死了。 - user2875404
19个回答

1216

是的,你可以做到。

在Swift中,你仍然可以使用"#if/#else/#endif"预处理器宏(尽管更受限制),正如苹果文档所述。这里是一个例子:

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

现在,你必须在其他地方设置“DEBUG”符号。在“Swift编译器 - 自定义标志”部分中,设置“其他Swift标志”行。您可以使用-D DEBUG条目添加DEBUG符号。

通常情况下,在Debug或Release模式下可以设置不同的值。

我在真实代码中测试过它,它可以工作;但在playground中似乎无法识别。

您可以在这里阅读我的原始帖子。


重要提示:-DDEBUG=1不起作用。只有-D DEBUG起作用。似乎编译器忽略了具有特定值的标志。


48
这是正确的答案,不过需要注意的是,你只能检查标志的存在性而不能检查特定的值。 - Charles Harley
24
除了按照上述要求添加-D DEBUG之外,你还需要在“Apple LLVM 6.0 - 预处理”->“预处理器宏”中定义DEBUG=1。请注意不要改变原来的意思,并尽量使翻译通俗易懂。 - Matthew Quiros
42
在我参考了这篇回答:https://dev59.com/xWAf5IYBdhLWcg3w9Wiu#24112024,并将格式更改为“-DDEBUG”之后,才使得它能够正常工作。 - Kramer
12
如果您不想在Objective-C代码中使用它,那么无需将DEBUG=1添加到Preprocessor Macros中。 - derpoliuk
9
你可以使用标准的布尔运算符(例如: #if !DEBUG )。 - Jean Le Moignan
显示剩余22条评论

411

Apple文档所述

Swift编译器不包括预处理器。相反,它利用编译时属性、构建配置和语言特性来完成相同的功能。因此,在Swift中不会导入预处理器指令。

我通过使用自定义Build Configurations实现了我想要的功能:

  1. 转到您的项目 / 选择您的目标 / Build Settings / 搜索Custom Flags
  2. 为您选择的目标设置自定义标志,使用-D前缀(无空格),适用于Debug和Release两种情况
  3. 对每个目标执行上述步骤

以下是检查目标的方法:

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

这里输入图片描述

使用Swift 2.2进行测试


4
  1. “with white space work also” 的意思是“带有空格也可以工作”。
  2. “should set the flag only for Debug?” 可以翻译成“应该只为调试设置标志吗?”
- c0ming
3
取决于你的需求,如果你只想在调试模式下执行某些操作而不是发布模式下执行,则需要从“Release”中删除“-DDEBUG”。请注意,这里涉及到编译标志。 - cdf1982
1
在我设置了自定义标志“-DLOCAL”后,在我的#if LOCAL #else #endif中,它会落入#else部分。我复制了原始目标AppTarget并将其重命名为AppTargetLocal,并设置了其自定义标志。 - Perwyl Liu
3
@Andrej,你知道如何让XCTest也能识别自定义标志吗?我意识到它会落入#if LOCAL中,当我在模拟器上运行时,而在测试期间则会落入#else中。我希望在测试期间它也能落入#if LOCAL中。 - Perwyl Liu
3
这应该成为被采纳的答案。当前被采纳的答案对Swift来说是错误的,因为它只适用于Objective-C。 - miken.mkndev
显示剩余3条评论

185

在许多情况下,您实际上不需要条件编译;您只需要可以开关的条件行为。为此,您可以使用环境变量。这具有巨大优势,即您实际上不必重新编译。

您可以在方案编辑器中设置环境变量,并轻松切换它:

enter image description here

您可以使用NSProcessInfo检索环境变量:

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

这里有一个真实的例子。我的应用程序只能在设备上运行,因为它使用音乐库,而模拟器上没有音乐库。那么,如何在我不拥有的设备上使用模拟器进行截屏呢?没有这些截屏,我就无法提交到AppStore。

我需要假数据不同的处理方式。我有两个环境变量:其中一个在开启时告诉应用程序在我的设备上运行时从真实数据生成假数据;另一个在开启时在模拟器上运行时使用假数据(而非缺失的音乐库)。每次切换这些特殊模式的开/关状态都很容易,因为在Scheme编辑器中有环境变量复选框。而且好处是,我无法在我的App Store构建中意外地使用它们,因为归档没有环境变量。


68
注意:环境变量适用于所有构建配置,不能为各个单独的配置设置不同的值。因此,如果你需要根据是发布版还是调试版来改变行为,这种方法 不可行。请注意保持原意并使语言更加通俗易懂。 - Eric
8
@Eric 同意,但并非适用于所有的方案操作。因此,在构建和运行时可以执行一项操作,在存档时执行另一项操作,这通常是您想要绘制的真实区别。或者您可以拥有多个方案,这也是一种常见的现实模式。另外,正如我在答案中所说,在方案中切换环境变量很容易。 - matt
11
在归档模式下,环境变量不起作用。它们只在从 XCode 启动应用程序时应用。如果您在设备上尝试访问它们,应用程序将崩溃。我是通过困难的方式发现这一点的。 - iupchris10
3
"@iupchris10“存档没有环境变量”是我上面回答的最后一句话。正如我在回答中所说,这是好的,这就是重点。" - matt
1
这是 XCTest 用例的确切解决方案,当应用程序在模拟器中运行时,您希望有一个默认行为,但在测试中严格控制行为。 - Stan
显示剩余11条评论

185

Xcode 8带来了关于ifdef替换的重大变化,即使用Active Compilation Conditions

请参考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中更多Swift Build Settings功能的信息,请参阅以下链接:http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


2
@Jonny 我找到的唯一方法是为该项目创建第三个构建配置。从“项目” > “信息”选项卡 > “配置”进入,点击“+”,然后复制“Debug”。然后,您可以自定义此配置的“活动编译条件”。别忘了编辑您的“Target” > “Test schemes”,以使用新的构建配置! - matthias
1
这应该是正确的答案...在使用Swift 4.x和xCode 9时,这是唯一有效的方法! - shokaveli
2
顺便提一下,在Xcode 9.3 Swift 4.1中,DEBUG已经在Active Compilation Conditions中了,您不需要添加任何内容来检查DEBUG配置。只需使用#if DEBUG和#endif即可。 - Denis Kutlubaev
我认为这既不是主题,也是一件坏事。你不想禁用活动编译条件。你需要一个新的不同的测试配置 - 它将不会有“Debug”标签。学习方案。 - Motti Shneor
@matthias 这正是我在寻找的。谢谢你。 - Dennis
显示剩余2条评论

99

从Swift 4.1开始,如果你只需要检查代码是使用调试还是发布配置构建,你可以使用内置函数:

  • _isDebugAssertConfiguration() (当优化设置为-Onone时返回true)
  • _isReleaseAssertConfiguration()(当优化设置为-O时返回true)(在Swift 3+中不可用)
  • _isFastAssertConfiguration() (当优化设置为-Ounchecked时返回true)

例如:

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

与预处理器宏相比,

  • ✓ 您无需定义自定义-D DEBUG标志即可使用它
  • ~ 它实际上是根据优化设置而定义的,而不是Xcode构建配置
  • ✗ 未记录文档,这意味着该功能可以在任何更新中被删除(但它应该是AppStore安全的,因为优化器将将其转换为常量)

    • 这些曾经被删除,但由于缺乏@testable属性而重新公开,对于未来的Swift命运尚不确定。
  • ✗ 在if / else中使用将始终生成“永远不会被执行”的警告。


1
这些内置函数是在编译时还是运行时评估的? - ma11hew28
2
我无法在发布版本中使用这些函数来选择性地排除一些仅用于调试的变量。 - Franklin Yu
3
这些函数有文档记录吗? - Tom Harrington
7
从Swift 3.0和XCode 8开始,这些函数已经无效。 - CodeBender
@CodeBender 在 Swift 3.1 中,只有 _isReleaseAssertConfiguration() 是无效的,其他两个又变成了公共的 由于一个 bug - kennytm
显示剩余4条评论

93

Xcode 8及以上版本

使用构建设置 / Swift编译器 - 自定义标志中的活动编译条件设置。

  • 这是将条件编译标志传递给Swift编译器的新构建设置。
  • 像这样简单地添加标志:ALPHABETA等。

然后像这样检查它:编译条件

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

提示:你也可以使用 #if !ALPHA 等。


请查看此文档以获取条件及其使用的完整列表:https://docs.swift.org/swift-book/ReferenceManual/Statements.html# - Cliff Ribaudo

81

Swift 没有预处理器。(首先,任意代码替换会破坏类型和内存安全性。)

不过,Swift 包括构建时配置选项,因此您可以根据特定平台或构建样式或响应您使用 -D 编译器参数定义的标志来有条件地包含代码。与 C 不同的是,您代码的有条件编译部分必须在语法上完整。关于这个问题,在《Using Swift With Cocoa and Objective-C》中有一节讨论。

例如:

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

38
首先,任意代码替换会破坏类型和内存安全。一个预处理器在编译器之前完成工作(因此得名),所以所有这些检查仍然可以进行。 - Thilo
10
@Thilo 我认为它破坏的是 IDE 支持。 - Aleksandr Dubinsky
1
我认为@rickster的意思是C预处理器宏不理解类型,它们的存在会破坏Swift的类型要求。宏在C中工作的原因是因为C允许隐式类型转换,这意味着您可以将INT_CONST放在任何接受float的地方。Swift不允许这样做。此外,如果您可以执行var floatVal = INT_CONST,那么当编译器期望一个Int但您将其用作FloatfloatVal的类型将被推断为Int)时,它最终会在某个地方崩溃。经过10次强制转换后,删除宏只是更清晰的选择... - Ephemera
我正在尝试使用这个功能,但似乎没有起作用,在iOS构建中仍在编译Mac代码。是否有其他设置屏幕需要调整? - Maury Markowitz
1
@Thilo 您是正确的 - 预处理器不会破坏任何类型或内存安全。 - tcurdt
显示剩余3条评论

58

根据活动编译条件设置isDebug常量

另一个也许更简单的解决方案,仍然可以得到一个布尔值,您可以将其传递到函数中,而不需要在代码库中遍布#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中使用不会生成“永远不会被执行”的警告。


52

Xcode 8个人建议:

a) 使用-D前缀的自定义标志可以正常工作,但是...

b) 更简单的用法:

Xcode 8中有一个新的部分:“活动编译条件”,已经有两行,用于调试和发布。

只需添加您的定义,而不需要使用-D


感谢您提到调试和发布有两行。 - Yitzchak
有人在发布版中测试过吗? - Glenn Posadas
这是更新后的 Swift 用户答案,即不包含“-D”。 - Mani
我曾尝试在“Other Swift Flags”中设置标志,但没有任何反应。感谢您建议将其设置在“Active Compilation Conditions”。它起作用了。 - Nguyễn Anh Tuấn

50

Moignans 这里的答案 可以正常工作。这里提供另外一些信息,以帮助理解。

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

您可以像以下这样否定宏:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接