#在Swift语言中替换ifdef

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

#ifdef DEBUG// Debug-only code#endif

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

327422 次浏览

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

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

例如:

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

是的,你能做到。

在Swift中,您仍然可以使用“#if/#else/#endif”预处理器宏(尽管更受限制),按照苹果文档。这是一个例子:

#if DEBUGlet a = 2#elselet a = 3#endif

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

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

我在真实的代码中测试了它,它是有效的;但它似乎在操场上无法识别。

你可以阅读我的原始文章这里


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

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

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

输入图片描述

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

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

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

我需要假数据不同的处理方式。我有两个环境变量:一个,打开时,告诉应用程序在我的设备上运行时从真实数据生成假数据;另一个,打开时,在模拟器上运行时使用假数据(不是缺失的曲库)。由于方案编辑器中的环境变量复选框,打开/关闭每种特殊模式都很容易。好处是我不会在我的App Store构建中意外使用它们,因为归档没有环境变量。

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

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

e. g.

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

与预处理器宏相比,

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

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

AppleDocs所示

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

我已经设法通过使用自定义构建配置来实现我想要的:

  1. 转到您的项目/选择您的目标/构建设置/搜索自定义标志
  2. 对于您选择的目标,使用-D前缀(没有空格)设置自定义标志,用于调试和发布
  3. 为您拥有的每个目标执行上述步骤

以下是检查目标的方法:

#if BANANAprint("We have a banana")#elseif MELONAprint("Melona")#elseprint("Kiwi")#endif

在此处输入图像描述

使用Swift 2.2测试

我对Xcode 8的两分钱:

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

更简单的使用:

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

简单地添加您的定义没有-D

ifdef替换的一个主要变化是Xcode 8.即使用主动编译条件

请参阅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 Build设置功能:http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

Xcode 8及以上

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

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

然后用编译条件检查它,如下所示:

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

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

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

另一个可能更简单的解决方案仍然会产生一个布尔值,您可以将其传递到函数中,而无需在整个代码库中添加#if条件,那就是将DEBUG定义为项目构建目标的Active Compilation Conditions之一,并包含以下内容(我将其定义为全局常量):

#if DEBUGlet isDebug = true#elselet isDebug = false#endif

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

这个概念建立在Kennytm的回答

与kennytm相比的主要优点是,它不依赖于私有或未记录的方法。

Swift4

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 = debugreturn isDebug}// assert:// "Condition is only evaluated in playgrounds and -Onone builds."// so isDebug is never changed to true in Release buildsassert(set(debug: true))return isDebug}()

与预处理器宏而Kennytm的答案相比,

  • 你不需要定义一个自定义的-D DEBUG标志来使用它
  • ~它实际上是根据优化设置定义的,而不是Xcode构建配置
  • 文档化,这意味着该函数将遵循正常的API发布/弃用模式。

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

这建立在依赖断言的乔恩·威利斯的答案之上,断言仅在调试编译中执行:

func Log(_ str: String) {assert(DebugLog(str))}func DebugLog(_ str: String) -> Bool {print(str)return true}

我的用例是记录打印语句。这是iPhoneX上发布版本的基准:

let iterations = 100_000_000let 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完全消除了函数调用。

GCC_PREPROCESSOR_DEFINITIONS构建设置中设置DEBUG=1后,我更喜欢使用函数进行以下调用:

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

然后在这个函数中包含我想在Debug构建中省略的任何块:

executeInProduction {Fabric.with([Crashlytics.self]) // Compiler checks this line even in Debug}

相比之下的优势:

#if !DEBUGFabric.with([Crashlytics.self]) // This is not checked, may not compile in non-Debug builds#endif

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

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

在代码

 #if Liveprint("Live")#elseprint("debug")#endif

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

#if DEBUG#endif

默认工作,因为在预处理器宏中DEBUG=1已经由Xcode设置。

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

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

希望将来Apple会为他们的书籍编写更详细的内容和索引。

XCODE 9及以上

#if DEVELOP//print("Develop")#elseif PRODCTN//print("Production")#else//#endif
func inDebugBuilds(_ code: () -> Void) {assert({ code(); return true }())}

来源

Moignans回答在这里工作得很好。这是另一条信息,以防它有帮助,

#if DEBUGlet a = 2#elselet a = 3#endif

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

#if !RELEASElet a = 2#elselet a = 3#endif

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

#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 DEBUGprint("Debug mode")#endif

Swift 5更新马特的回答

let dic = ProcessInfo.processInfo.environmentif dic["TRIPLE"] != nil {// ... do your secret stuff here ...}