Swift 语言中的 #ifdef 替换

#ifdef replacement in the Swift language

提问人:mxg 提问时间:6/3/2014 最后编辑:Aykhan Hagverdilimxg 更新时间:8/2/2023 访问量:345465

问:

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

#ifdef DEBUG
    // Debug-only code
#endif

Swift 中有类似的解决方案吗?

swift xcode 处理器指令

评论

1赞 Matej 1/10/2015
作为一个想法,你可以把它放在你的obj-c桥接头中。
75赞 David H 8/4/2016
你真的应该给出一个答案,因为你有几个答案可供选择,这个问题让你得到了很多赞成票。
1赞 David H 6/11/2020
@Userthatisnotauser你完全错过了重点。你问一个问题,你会得到很好的答案 - 选择一个。不要忽视时间和精力。
1赞 ReinstateMonica3167040 6/11/2020
@DavidH 不,实际上情况恰恰相反。我的评论只是搭便车者对 42 的参考。我完全同意,并想投赞成票,但我不能让自己成为第 43 名。
2赞 David H 6/11/2020
@Userthatisnotauser海报有 19k 分——人们投票给他的答案,但他似乎并不关心帮助他的人。我总是总是选择一个答案。

答:

81赞 rickster 6/3/2014 #1

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

不过,Swift 确实包含构建时配置选项,因此您可以有条件地包含某些平台或构建样式的代码,或者响应您使用编译器参数定义的标志。但是,与 C 不同的是,代码的有条件编译部分必须在语法上是完整的。在将 Swift 与 Cocoa 和 Objective-C 一起使用中有一节是关于这一点的。-D

例如:

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

评论

38赞 Thilo 6/4/2014
“首先,任意代码替换会破坏类型和内存的安全性。预处理器不是在编译器之前完成工作(因此得名)吗?因此,所有这些检查仍然可以进行。
10赞 Aleksandr Dubinsky 6/5/2014
@Thilo我认为它破坏的是IDE支持
1赞 Ephemera 6/7/2014
我认为@rickster所得到的是 C 预处理器宏不了解类型,它们的存在会破坏 Swift 的类型要求。宏之所以在 C 中工作,是因为 C 允许隐式类型转换,这意味着你可以把你的任何地方都接受。斯威夫特不允许这样做。此外,如果你能不可避免地这样做,它会在编译器期望的某个地方崩溃,但你把它用作(类型将被推断为 )。10 次投射后,删除宏更干净......INT_CONSTfloatvar floatVal = INT_CONSTIntFloatfloatValInt
0赞 Maury Markowitz 2/22/2015
我正在尝试使用它,但它似乎不起作用,它仍在 iOS 版本上编译 Mac 代码。某处是否有另一个设置屏幕需要调整?
1赞 tcurdt 6/10/2016
@Thilo您是对的 - 预处理器不会破坏任何类型或内存安全。
1216赞 Jean Le Moignan 6/11/2014 #2

是的,你可以做到。

在 Swift 中,您仍然可以使用 “#if/#else/#endif” 预处理器宏(尽管受到更多限制),根据 Apple 文档。下面是一个示例:

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

但是,现在您必须在其他地方设置“DEBUG”符号。在“Swift 编译器 - 自定义标志”部分的“其他 Swift 标志”行中设置它。将 DEBUG 符号添加到条目中。-D DEBUG

像往常一样,可以在“调试”或“发布”中设置不同的值。

我在真实代码中对其进行了测试,它有效;不过,在操场上似乎没有被认出来。

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


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

评论

48赞 Charles Harley 6/18/2014
这是正确答案,但应该注意的是,您只能检查标志的存在,而不能检查特定值。
24赞 Matthew Quiros 3/26/2015
附加说明:除了如上所述添加之外,您还需要在 -> 中定义。-D DEBUGDEBUG=1Apple LLVM 6.0 - PreprocessingPreprocessor Macros
42赞 Kramer 7/24/2015
直到我将格式从这个答案更改为:stackoverflow.com/a/24112024/747369,我才能让它工作。-DDEBUG
12赞 derpoliuk 11/16/2015
@MattQuiros 如果您不想在 Objective-C 代码中使用它,则无需添加 。DEBUG=1Preprocessor Macros
9赞 Jean Le Moignan 4/22/2016
@Daniel 您可以使用标准布尔运算符(例如:' #if !调试 ' )
185赞 matt 6/22/2014 #3

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

您可以在方案编辑器中设置环境变量,并轻松打开或关闭它:

enter image description here

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

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

这是一个现实生活中的例子。我的应用仅在设备上运行,因为它使用模拟器上不存在的音乐库。那么,如何在模拟器上为我不拥有的设备拍摄屏幕截图呢?没有这些屏幕截图,我无法提交到 AppStore。

我需要假数据不同的处理方式。我有两个环境变量:一个是打开时,它会告诉应用程序在我的设备上运行时从真实数据生成假数据;另一个,当打开时,在模拟器上运行时使用虚假数据(而不是丢失的音乐库)。由于 Scheme 编辑器中的环境变量复选框,可以轻松打开/关闭这些特殊模式。好处是,我不会在我的 App Store 版本中意外使用它们,因为存档没有环境变量。

评论

68赞 Eric 6/10/2015
注意:环境变量是为所有构建配置设置的,不能为单个配置设置。因此,如果您需要根据是发布版本还是调试版本来更改行为,这不是一个可行的解决方案。
8赞 matt 6/10/2015
@Eric 同意,但并未针对所有方案操作设置它们。因此,您可以在构建和运行上做一件事,在存档上做另一件事,这通常是您想要在现实生活中做出的区分。或者你可以有多个方案,这也是现实生活中的常见模式。另外,正如我在回答中所说,在方案中打开和关闭环境变量很容易。
11赞 iupchris10 9/15/2015
环境变量在存档模式下不起作用。仅当从 XCode 启动应用时,它们才适用。如果您尝试在设备上访问这些内容,应用程序将崩溃。发现艰难的方式。
3赞 matt 9/15/2015
@iupchris10 “存档没有环境变量”是我上面回答的最后一句话。正如我在回答中所说,这很好。这就是重点
1赞 Stan 6/3/2017
对于 XCTest 情况,这完全是正确的解决方案,在这种情况下,当应用程序在模拟器中运行时,您希望使用默认行为,但希望严格控制测试中的行为。
99赞 kennytm 12/30/2015 #4

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

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

例如

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

与预处理器宏相比,

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

  • ✗ 在 if/else 中使用将始终生成“永远不会执行”警告。

评论

1赞 ma11hew28 2/29/2016
这些内置函数是在编译时还是在运行时进行评估?
0赞 kennytm 3/1/2016
@MattDiPasquale优化时间。 将在发布模式下进行评估,并且是调试模式。if _isDebugAssertConfiguration()if falseif true
2赞 Franklin Yu 4/12/2016
不过,我不能使用这些函数在版本中选择退出一些仅调试的变量。
3赞 Tom Harrington 5/3/2016
这些函数是否记录在某处?
7赞 CodeBender 11/4/2016
从 Swift 3.0 和 XCode 8 开始,这些函数无效。
411赞 Andrej 4/8/2016 #5

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

enter image description here

使用 Swift 2.2 进行测试

评论

4赞 c0ming 4/25/2016
1.空白工作也,2.应该只为调试设置标志吗?
3赞 cdf1982 7/18/2016
@c0ming这取决于您的需求,但如果您希望仅在调试模式下而不是在发布中发生某些事情,则需要从 Release 中删除 -DDEBUG。
1赞 Perwyl Liu 7/20/2016
在我设置自定义标志后,它属于该部分。我复制了原始目标并将其重命名为并设置其自定义标志。-DLOCAL#if LOCAl #else #endif#elseAppTargetAppTargetLocal
3赞 Perwyl Liu 7/20/2016
@Andrej您碰巧知道如何让 XCTest 识别自定义标志吗?我意识到它落入了,当我使用模拟器运行时的预期结果,并在测试期间陷入。我希望它在测试期间也落入其中。#if LOCAL #else #if LOCAL
3赞 miken.mkndev 9/3/2016
这应该是公认的答案。目前接受的答案对 Swift 来说是不正确的,因为它只适用于 Objective-C。
52赞 ingconti 9/4/2016 #6

我对 Xcode 8 的两分钱:

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

b) 使用更简单:

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

只需添加您的定义 WITHOUT .-D

评论

0赞 Yitzchak 11/13/2016
感谢您提到有两行用于调试和发布
0赞 Glenn Posadas 11/21/2016
有人在发布中测试过吗?
0赞 Mani 12/19/2017
这是 swift 用户的更新答案。即没有.-D
0赞 Nguyễn Anh Tuấn 9/12/2020
我曾试图在“其他 Swift 标志”中设置标志,但什么也没发生。感谢您建议将其设置为“活动编译条件”。它有效。
185赞 DShah 9/11/2016 #7

Xcode 8 带来了一个重大的替换变化。即使用活动编译条件ifdef

请参阅《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 传递给新设置。是时候移动所有这些条件编译值了!

请参阅 Xcode 8 中更多 Swift 构建设置功能的链接: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

评论

0赞 Jonny 9/1/2017
有没有办法在构建时禁用设置的活动编译条件?在构建用于测试的调试配置时,我需要禁用 DEBUG 条件。
2赞 matthias 10/11/2017
@Jonny 我发现的唯一方法是为项目创建第三个构建配置。从“项目>信息”选项卡>“配置”中,点击“+”,然后复制“调试”。然后,您可以自定义此配置的活动编译条件。不要忘记编辑 Target > Test 方案以使用新的构建配置!
1赞 shokaveli 4/12/2018
这应该是正确答案。这是使用 Swift 9 在 xCode 4.x 上唯一对我有用的东西!
2赞 Denis Kutlubaev 6/3/2018
顺便说一句,在 Xcode 9.3 中,Swift 4.1 DEBUG 已经存在于活动编译条件中,您无需添加任何内容即可检查 DEBUG 配置。只需 #if 调试并 #endif。
0赞 Motti Shneor 6/18/2018
我认为这既是题外话,也是一件坏事。 您不想禁用活动编译条件。您需要一个新的和不同的配置来进行测试 - 它不会有“调试”标签。了解方案。
93赞 Jakub Truhlář 1/13/2017 #8

Xcode 8 及更高版本

使用“生成设置”/“Swift 编译器 - 自定义标志”中的“活动编译条件”设置。

  • 这是用于将条件编译标志传递给 Swift 编译器的新构建设置。
  • 只需添加如下标志:等。ALPHABETA

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

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

提示:您也可以使用 #if !ALPHA

评论

0赞 Cliff Ribaudo 3/13/2022
有关条件及其使用的完整列表,请参阅此文档:docs.swift.org/swift-book/ReferenceManual/Statements.html#
58赞 Jon Willis 11/23/2017 #9

基于活动编译条件的 isDebug 常量

另一个可能更简单的解决方案仍然会产生一个布尔值,你可以将它传递到函数中,而无需在整个代码库中加入条件,那就是定义为你的项目构建目标之一,并包含以下内容(我将其定义为全局常量):#ifDEBUGActive 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 中使用不会生成“永远不会执行”警告。

2赞 Warren Stringer 12/15/2017 #10

这建立在 Jon Willis 的答案之上,该答案依赖于 assert,该 assert 仅在 Debug 编译中执行:

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 完全消除了函数调用。

评论

0赞 Johan 4/24/2018
消除,就像在不调试时完全删除调用一样 - 由于函数为空?那就太完美了。
8赞 E. Rivera 2/8/2018 #11

在构建设置中设置后,我更喜欢使用函数进行以下调用:DEBUG=1GCC_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

编译器检查我的代码的语法,所以我确定它的语法是正确的并构建的。

5赞 sachin_kvk 2/21/2018 #12

![在 Xcode 8 及更高版本中,转到构建设置>搜索自定义标志 ]1

在代码中

 #if Live
    print("Live")
    #else
    print("debug")
    #endif

评论

1赞 Dale 5/18/2019
你已经在这里击中了它!Swift #if 查看自定义标志,而不是预处理器宏。请使用链接中的内容更新您的答案,通常链接会在一段时间后断开
29赞 Vadim Motorine 9/8/2018 #13

在使用 Xcode 版本 9.4.1、Swift 4.1 创建的 Swift 项目中

#if DEBUG
#endif

默认情况下有效,因为在预处理器宏中,Xcode 已经设置了 DEBUG=1。

因此,您可以“开箱即用”地使用 #if DEBUG。

顺便说一句,如何使用条件编译块一般写在 Apple 的书 The Swift Programming Language 4.1 (编译器控制语句部分) 中,如何编写编译标志以及 Swift 中 C 宏的对应物写在另一本 Apple 的书 Using Swift with Cocoa and Objective C (在预处理器指令部分)

希望将来苹果能为他们的书写出更详细的内容和索引。

25赞 midhun p 9/14/2018 #14

XCODE 9 及更高版本

#if DEVELOP
    //print("Develop")
#elseif PRODUCTION
    //print("Production")
#else
    //
#endif
4赞 Adam Smaka 11/30/2018 #15
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

评论

1赞 Shayne 1/18/2019
这不是条件编译。虽然有用,但它只是一个普通的旧运行时条件。OP 在编译后出于元编程目的而请求
3赞 mojuba 5/12/2019
只需在前面添加,这将是 Swift 最优雅和最惯用的方式。在发布版本中,您的块将被优化并完全消除。苹果自己的蔚来框架中也使用了类似的功能。@inlinablefunccode()
50赞 Sazzad Hissain Khan 5/21/2019 #16

Moignans在这里的回答很好。这是另一条信息,以防万一,

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

您可以像下面这样否定宏,

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif
25赞 Mojtaba Hosseini 8/5/2020 #17

有一些处理器会接受参数,我在下面列出了它们。您可以根据需要更改参数:

#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
0赞 Skron31 11/9/2021 #18

Swift 5 更新了 Matt 的答案

let dic = ProcessInfo.processInfo.environment
if dic["TRIPLE"] != nil {
// ... do your secret stuff here ...
}
0赞 devdchaudhary 8/2/2023 #19

可以创建名为 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")

}