Swift语言中的#ifdef替换


735

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

#ifdef DEBUG
    // Debug-only code
#endif

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


1
作为一个想法,你可以把这个在您的OBJ-C桥接头..
马捷

42
您确实应该给一个答案,因为有多种选择,而这个问题已使您获得很多票。
David H

Answers:


1069

是的,你可以做到。

在Swift中,按照Apple docs的规定,您仍然可以使用“#if /#else /#endif”预处理器宏(尽管有更多限制)。这是一个例子:

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

现在,您必须在其他位置设置“ DEBUG”符号。在“ Swift编译器-自定义标志”部分的“其他Swift标志”行中进行设置。您将DEBUG符号与-D DEBUG条目中。

与往常一样,您可以在Debug或Release中设置不同的值。

我用真实代码对其进行了测试,并且可以正常工作。它似乎在操场上似乎未被认可。

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


重要说明: -DDEBUG=1不起作用。仅-D DEBUG适用。似乎编译器正在忽略具有特定值的标志。


41
尽管应该注意,您只能检查标志的存在,但不能检查特定值,但这是正确的答案。
Charles Harley 2014年

19
附加说明:除了-D DEBUG上述添加之外,您还需要DEBUG=1Apple LLVM 6.0 - Preprocessing->中定义Preprocessor Macros
马修·基罗斯

38
在将格式更改-DDEBUG为以下答案之前,我无法使它起作用:stackoverflow.com/a/24112024/747369
克莱默2015年

11
@MattQuiros有没有必要添加DEBUG=1Preprocessor Macros,如果你不想在Objective-C代码来使用它。
derpoliuk 2015年

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测试


4
1.也有空白功能,2.应该只为调试设置标志吗?
c0ming 2016年

3
@ c0ming它取决于您的需求,但是如果您只想在调试模式下而不是在发布模式下发生某些事情,则需要从发布中删除-DDEBUG。
cdf1982 '16

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's

171

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

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

在此处输入图片说明

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

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

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

我需要伪造数据其他处理方式。我有两个环境变量:一个在打开时告诉应用程序在设备上运行时从真实数据生成假数据;另一种是在模拟器上运行时打开时使用伪造的数据(而不是缺少的音乐库)。通过使用方案编辑器中的环境变量复选框,可以轻松打开/关闭这些特殊模式。而且好处是,我不会在App Store构建中意外使用它们,因为归档没有环境变量。


由于某种原因,我的环境变量在第二次启动应用程序时返回nil
Eugene

60
注意:环境变量是为所有构建配置设置的,不能为单个构建设置。因此,如果您需要根据行为是发行版还是调试版来更改其行为,则这不是可行的解决方案。
埃里克(Eric)2015年

5
@Eric同意,但未为所有方案操作都设置它们。因此,您可以在构建和运行时做一件事,而在存档中做另一件事,这通常是您想要绘制的真实生活的区别。或者,您可以有多种方案,这也是现实生活中的常见模式。另外,正如我在回答中所说,在方案中打开和关闭环境变量很容易。
马特2015年

10
环境变量在归档模式下不起作用。仅在从XCode启动应用程序时才应用它们。如果您尝试在设备上访问这些文件,则该应用程序将崩溃。找出困难的方法。
iupchris10

2
@ iupchris10“存档没有环境变量”是我上面回答的最后一句话。就像我在回答中所说的那样,那很好。这就是重点
马特2015年

159

ifdefXcode 8提出了一项重大的替换变更,即使用Active Compilation Conditions

请参阅建筑及链接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传递给新设置。现在该移动所有这些条件编译值了!

请参考以下链接,了解Xcode 8中更多的Swift Build设置功能:http : //www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


无论如何,在构建时是否要禁用设置的活动编译条件?构建用于测试的调试配置时,我需要禁用DEBUG条件。
乔尼

1
@Jonny我发现的唯一方法是为项目创建第3个构建配置。从项目>信息选项卡>配置中,单击“ +”,然后复制调试。然后,您可以为此配置自定义“活动编译条件”。别忘了编辑目标>测试方案以使用新的构建配置!
matthias

1
这应该是正确的答案..这是唯一使用Swift 4.x在xCode 9上对我有用的东西!
shokaveli

1
顺便说一句,在Xcode 9.3中,Swift 4.1的“调试”已经在“活动编译条件”中,您无需添加任何内容即可检查DEBUG的配置。仅#if DEBUG和#endif。
Denis Kutlubaev

我认为这既是题外话,也是一件坏事。您不想禁用“活动编译条件”。您需要进行测试的新配置和其他配置-上面没有“ Debug”标签。了解方案。
Motti Shneor

93

从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构建配置定义的
  • ✗未记录,这意味着该功能可以在任何更新中删除(但它应该是AppStore安全的,因为优化程序会将其转换为常量)

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


1
这些内置函数是否在编译时或运行时进行评估?
ma11hew28 '16

@MattDiPasquale优化时间。if _isDebugAssertConfiguration()将被评估为if false处于释放模式,并且处于if true调试模式。
kennytm '16

2
但是,我无法使用这些功能在发行版中选择退出某些仅调试变量。
富兰克林于

3
这些功能是否记录在某处?
汤姆·哈灵顿

7
从Swift 3.0和XCode 8开始,这些功能无效。
CodeBender

86

Xcode 8及以上

使用Build settings / Swift编译器-Custom标志中的Active Compilation Conditions设置。

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

然后使用如下编译条件进行检查:

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

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


77

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

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

例如:

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

34
“一方面,任意代码替换都会破坏类型和内存安全性。” 预处理程序不是在编译器之前做其工作(因此得名)吗?因此,所有这些检查仍然可以进行。
Thilo 2014年

10
@Thilo我认为它打破了对IDE的支持
Aleksandr Dubinsky

1
我认为@rickster的意思是C预处理器宏不了解类型,它们的存在会破坏Swift的类型要求。宏在C中起作用的原因是因为C允许隐式类型转换,这意味着您可以将INT_CONSTa 放置在任何float可接受的位置。Swift不允许这样做。此外,如果您var floatVal = INT_CONST不可避免地要这样做,则稍后在编译器期望an时,它将在某处崩溃,Int但您将其用作Float(类型floatVal将被推断为Int)。10次​​转换之后,它更干净了,可以删除宏...
Ephemera 2014年

我正在尝试使用它,但是它似乎没有用,它仍然在iOS版本上编译Mac代码。是否需要调整另一个设置屏幕?
莫里·马克维兹

1
@Thilo,您是正确的-预处理程序不会破坏任何类型或内存安全性。
tcurdt

50

我对Xcode 8的两美分:

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

b)使用更简单:

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

只需添加您的定义而无需 -D


感谢您提到有两个行进行调试和发布
Yitzchak

有人测试过此版本吗?
Glenn

这是快速用户的最新答案。即没有-D
玛尼

46

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

另一个可能更简单的解决方案仍然#if定义DEBUG为您的项目构建目标之一,Active Compilation Conditions并包含以下内容(我将其定义为全局常量):

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

基于编译器优化设置的isDebug常量

这个概念建立在肯尼TM的答案之上

与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中使用in 不会产生“永远不会执行”警告。


25

Moignans 在这里的回答很好。如果有帮助,这里还有另一条信息,

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

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

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

23

在使用Xcode 9.4.1创建的Swift项目中,Swift 4.1

#if DEBUG
#endif

默认情况下有效,因为Xcode已经在Preprocessor Macros中设置了DEBUG = 1。

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

顺便说一句,如何在Apple的书《 Swift编程语言4.1》(“编译器控制语句”部分)中编写了一般如何使用条件编译块的方法,以及如何编写编译标志以及Swift中C宏的对应内容的内容。苹果公司的另一本书《将Swift与Cocoa和Objective C结合使用(在Preprocessor Directives部分中)

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


17

XCODE 9及以上

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

3
哇,这是我见过的最丑陋的缩写: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

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



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

资源


1
这不是条件编译。虽然有用,但它只是一个普通的旧运行时条件。OP正在询问用于元编程目的的编译时间
Shayne

3
只需@inlinable在前面添加,func这将是Swift最优雅,最惯用的方式。在发行版本中,您的code()模块将被优化并完全消除。苹果自己的NIO框架中使用了类似的功能。
mojuba

1

这是基于Jon Willis的依赖断言答案而建立的,仅在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完全消除了函数调用。


消除,因为不在函数中时完全删除了调用-由于函数为空?那将是完美的。
约翰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.