在 C/C++/Objective C 中,您可以使用编译器预处理器定义宏。此外,您可以使用编译器预处理器包含/排除某些代码部分。
#ifdef DEBUG
// Debug-only code
#endif
Swift 中是否有类似的解决方案?
是的,你可以做到。
在 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
有效。似乎编译器忽略了具有特定值的标志。
如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 测试
-DLOCAL
后,在我的 #if LOCAl #else #endif
上,它属于 #else
部分。我复制了原始目标 AppTarget
并将其重命名为 AppTargetLocal
&设置其自定义标志。
#if LOCAL
,这是我使用模拟器运行时的预期结果,在测试期间属于 #else
。我希望它在测试期间也属于 #if LOCAL
。
在很多情况下,你并不真的需要条件编译;您只需要可以打开和关闭的条件行为。为此,您可以使用环境变量。这具有您实际上不必重新编译的巨大优势。
您可以在方案编辑器中设置环境变量,并轻松打开或关闭它:
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 构建中意外使用它们,因为归档没有环境变量。
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/
从 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 中使用总是会产生“将永远不会被执行”的警告。
if _isDebugAssertConfiguration()
将在发布模式下评估为 if false
,而 if true
是调试模式。
Xcode 8 及以上
在 Build settings / Swift compiler - Custom flags 中使用 Active Compilation Conditions 设置。
这是将条件编译标志传递给 Swift 编译器的新构建设置。
像这样简单添加标志:ALPHA、BETA 等。
然后使用 compilation conditions 进行检查,如下所示:
#if ALPHA
//
#elseif BETA
//
#else
//
#endif
提示:您也可以使用 #if !ALPHA 等。
没有 Swift 预处理器。 (一方面,任意代码替换破坏了类型和内存安全。)
不过,Swift 确实包含构建时配置选项,因此您可以有条件地包含某些平台或构建样式的代码,或者响应您使用 -D
编译器参数定义的标志。但是,与 C 不同的是,代码的条件编译部分必须在语法上是完整的。 Using Swift With Cocoa and Objective-C 中有一个关于此的部分。
例如:
#if os(iOS)
let color = UIColor.redColor()
#else
let color = NSColor.redColor()
#endif
INT_CONST
放在任何可以接受 float
的地方。斯威夫特不允许这样做。此外,如果您可以不可避免地执行 var floatVal = INT_CONST
,那么当编译器需要 Int
但您将其用作 Float
(floatVal
的类型将被推断为 Int
)时,它会在稍后的某个地方崩溃。 10 次之后,它只是更清洁地删除宏......
基于活动编译条件的 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 中使用不会产生“永远不会被执行”的警告。
Xcode 8 我的两分钱:
a) 使用 -D
前缀的自定义标志可以正常工作,但是...
b) 更简单的使用:
在 Xcode 8 中有一个新部分:“Active Compilation Conditions”,已经有两行,用于调试和发布。
只需添加您的定义而不使用 -D
。
-D
。
Moignans answer 在这里工作正常。这是另一条信息,以防万一,
#if DEBUG
let a = 2
#else
let a = 3
#endif
您可以像下面这样否定宏,
#if !RELEASE
let a = 2
#else
let a = 3
#endif
在使用 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(在预处理器指令部分)
希望未来苹果能为他们的书写出更详细的内容和索引。
XCODE 9 及更高版本
#if DEVELOP
//
#elseif PRODCTN
//
#else
//
#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 DEBUG
print("Debug mode")
#endif
在您的 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
是编译器检查我的代码的语法,所以我确信它的语法是正确的并且可以构建。
![In Xcode 8 & above go to build setting -> search for custom flags ]1
在代码中
#if Live
print("Live")
#else
print("debug")
#endif
func inDebugBuilds(_ code: () -> Void) {
assert({ code(); return true }())
}
func
前添加 @inlinable
,这将是 Swift 最优雅和惯用的方式。在发布版本中,您的 code()
块将被优化并完全消除。苹果自己的 NIO 框架中使用了类似的功能。
这建立在依赖于断言的 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 完全消除了函数调用。
matt's answer 的 Swift 5 更新
let dic = ProcessInfo.processInfo.environment
if dic["TRIPLE"] != nil {
// ... do your secret stuff here ...
}
-D DEBUG
之外,您还需要在Apple LLVM 6.0 - Preprocessing
中定义DEBUG=1
->Preprocessor Macros
。-DDEBUG
之前,我无法使其工作:stackoverflow.com/a/24112024/747369。DEBUG=1
添加到Preprocessor Macros
。