ChatGPT解决这个技术问题 Extra ChatGPT

Swift 语言中的#ifdef 替换

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

#ifdef DEBUG
    // Debug-only code
#endif

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

作为一个想法,你可以把它放在你的 obj-c 桥接头中。
你真的应该奖励一个答案,因为你有几个可供选择,这个问题已经让你获得了很多赞成票。
@Userthatisnotauser 你完全错过了重点。你问一个问题,你会得到很好的答案——选择一个。不要忽视时间和精力。
@DavidH 不,实际上是相反的。我的评论只是 Hitchhiker 对 42 的参考。我完全同意,并想投赞成票,但我不能让自己成为第 43 名。
@Userthatisnotauser 张贴者有 19k 分 - 人们投票给他的答案,但他似乎并不关心帮助他的人。我总是选择一个答案。

C
Community

是的,你可以做到。

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

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

现在,您必须在别处设置“DEBUG”符号。在“Swift Compiler - Custom Flags”部分的“Other Swift Flags”行中设置它。使用 -D DEBUG 条目添加 DEBUG 符号。

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

我用真实代码对其进行了测试,它可以工作;不过,它似乎在操场上并没有被认出来。

您可以阅读我的原始帖子 here

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


这是正确的答案,但应该注意的是,您只能检查标志的存在,但不能检查特定值。
补充说明:除了如上所述添加 -D DEBUG 之外,您还需要在 Apple LLVM 6.0 - Preprocessing 中定义 DEBUG=1 -> Preprocessor Macros
在我将此答案的格式更改为 -DDEBUG 之前,我无法使其工作:stackoverflow.com/a/24112024/747369
@MattQuiros 如果您不想在 Objective-C 代码中使用它,则无需将 DEBUG=1 添加到 Preprocessor Macros
@Daniel您可以使用标准布尔运算符(例如:`#if !DEBUG`)
D
Daniel

Apple Docs中所述

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

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

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

以下是检查目标的方法:

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

https://i.stack.imgur.com/zAKzJ.png

使用 Swift 2.2 测试


1.空白也可以工作,2.应该只为调试设置标志吗?
@c0ming 这取决于您的需要,但是如果您希望仅在调试模式下而不是在发布模式下发生某些事情,则需要从发布中删除 -DDEBUG。
在我设置自定义标志 -DLOCAL 后,在我的 #if LOCAl #else #endif 上,它属于 #else 部分。我复制了原始目标 AppTarget 并将其重命名为 AppTargetLocal &设置其自定义标志。
@Andrej您是否碰巧知道如何让 XCTest 也识别自定义标志?我意识到它属于 #if LOCAL ,这是我使用模拟器运行时的预期结果,在测试期间属于 #else 。我希望它在测试期间也属于 #if LOCAL
这应该是公认的答案。当前接受的答案对于 Swift 是不正确的,因为它仅适用于 Objective-C。
C
Crashalot

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

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

https://i.stack.imgur.com/opfbT.png

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

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

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

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


注意:环境变量是为所有构建配置设置的,不能为单个配置设置。因此,如果您需要根据是发布版本还是调试版本来更改行为,这不是一个可行的解决方案。
@Eric 同意,但并非为所有方案操作设置它们。因此,您可以在构建和运行时做一件事,而在存档上做另一件事,这通常是您想要绘制的现实生活中的区别。或者你可以有多个方案,这也是现实生活中的常见模式。另外,正如我在回答中所说,在方案中打开和关闭环境变量很容易。
环境变量在存档模式下不起作用。它们仅在从 XCode 启动应用程序时应用。如果您尝试在设备上访问这些,应用程序将崩溃。发现了艰难的方式。
@iupchris10“归档没有环境变量”是我上面回答的最后一句话。正如我在回答中所说,这很好。这是重点。
这正是 XCTest 案例的正确解决方案,您希望应用程序在模拟器中运行时具有默认行为,但您希望严格控制测试中的行为。
D
DShah

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/


无论如何在构建时禁用设置的活动编译条件?在构建调试配置以进行测试时,我需要禁用 DEBUG 条件。
@Jonny 我发现的唯一方法是为项目创建第三个构建配置。从 Project > Info 选项卡 > Configurations,点击“+”,然后复制 Debug。然后,您可以为此配置自定义活动编译条件。不要忘记编辑您的目标 > 测试方案以使用新的构建配置!
这应该是正确的答案..它是唯一对我使用 Swift 4.x 的 xCode 9 有用的东西!
顺便说一句,在 Xcode 9.3 Swift 4.1 DEBUG 中已经存在 Active Compilation Conditions,您无需添加任何内容来检查 DEBUG 配置。只需#if DEBUG 和#endif。
我认为这既是题外话,也是一件坏事。您不想禁用活动编译条件。您需要一个新的和不同的测试配置 - 上面不会有“调试”标签。了解方案。
k
kennytm

从 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 构建配置

✗ 未记录,这意味着该函数可以在任何更新中删除(但它应该是 AppStore 安全的,因为优化器会将这些转换为常量)这些一旦删除,但由于缺少 @testable 属性而重新公开,未来命运不确定迅速。

这些曾经被删除,但由于缺少 @testable 属性而重新公开,未来的 Swift 命运不确定。

✗ 在 if/else 中使用总是会产生“将永远不会被执行”的警告。


这些内置函数是在编译时还是运行时评估的?
@MattDiPasquale 优化时间。 if _isDebugAssertConfiguration() 将在发布模式下评估为 if false,而 if true 是调试模式。
不过,我不能使用这些函数在发布中选择退出某些仅调试变量。
这些功能是否记录在某处?
从 Swift 3.0 和 XCode 8 开始,这些函数无效。
J
Jakub Truhlář

Xcode 8 及以上

在 Build settings / Swift compiler - Custom flags 中使用 Active Compilation Conditions 设置。

这是将条件编译标志传递给 Swift 编译器的新构建设置。

像这样简单添加标志:ALPHA、BETA 等。

然后使用 compilation conditions 进行检查,如下所示:

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

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


有关条件及其使用的完整列表,请参阅此文档:docs.swift.org/swift-book/ReferenceManual/Statements.html#
r
rickster

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

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

例如:

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

“一方面,任意代码替换破坏了类型和内存安全。”预处理器不是在编译器之前完成它的工作吗(因此得名)?所以所有这些检查仍然可以进行。
@Thilo 我认为它破坏的是 IDE 支持
我认为@rickster 的意思是 C 预处理器宏不了解类型,它们的存在会破坏 Swift 的类型要求。宏在 C 中起作用的原因是因为 C 允许隐式类型转换,这意味着您可以将 INT_CONST 放在任何可以接受 float 的地方。斯威夫特不允许这样做。此外,如果您可以不可避免地执行 var floatVal = INT_CONST,那么当编译器需要 Int 但您将其用作 FloatfloatVal 的类型将被推断为 Int)时,它会在稍后的某个地方崩溃。 10 次之后,它只是更清洁地删除宏......
我正在尝试使用它,但它似乎不起作用,它仍在编译 iOS 版本上的 Mac 代码。是否有另一个设置屏幕需要调整?
@Thilo 你是对的 - 预处理器不会破坏任何类型或内存安全。
J
Jon Willis

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

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

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

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

这个概念建立在 kennytm's answer

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


P
Peter Mortensen

Xcode 8 我的两分钱:

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

b) 更简单的使用:

在 Xcode 8 中有一个新部分:“Active Compilation Conditions”,已经有两行,用于调试和发布。

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


感谢您提到调试和发布有两行
有人在发布时测试过这个吗?
这是 swift 用户的最新答案。即没有-D
我曾尝试在“Other Swift Flags”中设置标志,但什么也没发生。感谢您建议将其设置为“活动编译条件”。有用。
C
Codie CodeMonkey

Moignans answer 在这里工作正常。这是另一条信息,以防万一,

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

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

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

V
Vadim Motorine

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

#if DEBUG
#endif

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

所以你可以使用#if DEBUG“开箱即用”。

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

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


k
kuzdu

XCODE 9 及更高版本

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

M
Mojtaba Hosseini

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

#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

R
Rivera

在您的 GCC_PREPROCESSOR_DEFINITIONS 构建设置中设置 DEBUG=1 后,我更喜欢使用函数进行此调用:

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

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


s
sachin_kvk

![In Xcode 8 & above go to build setting -> search for custom flags ]1

在代码中

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

你在这里找到了! Swift #if 查看自定义标志而不是预处理器宏。请使用链接中的内容更新您的答案,通常链接会在一段时间后断开
A
Adam Smaka
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

Source


这不是条件编译。虽然有用,但它只是一个普通的旧运行时条件。 OP 在编译时间后询问元编程目的
只需在 func 前添加 @inlinable,这将是 Swift 最优雅和惯用的方式。在发布版本中,您的 code() 块将被优化并完全消除。苹果自己的 NIO 框架中使用了类似的功能。
W
Warren Stringer

这建立在依赖于断言的 Jon Willis's 答案之上,该答案仅在调试编译中执行:

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


消除,因为在不调试时完全删除调用 - 由于函数为空?那将是完美的。
S
Skron31

matt's answer 的 Swift 5 更新

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