提问人:mxg 提问时间:6/3/2014 最后编辑:Aykhan Hagverdilimxg 更新时间:8/2/2023 访问量:345465
Swift 语言中的 #ifdef 替换
#ifdef replacement in the Swift language
问:
在 C/C++/Objective C 中,您可以使用编译器预处理器定义宏。 此外,您可以使用编译器预处理器包含/排除代码的某些部分。
#ifdef DEBUG
// Debug-only code
#endif
Swift 中有类似的解决方案吗?
答:
没有 Swift 预处理器。(首先,任意代码替换会破坏类型和内存的安全性。
不过,Swift 确实包含构建时配置选项,因此您可以有条件地包含某些平台或构建样式的代码,或者响应您使用编译器参数定义的标志。但是,与 C 不同的是,代码的有条件编译部分必须在语法上是完整的。在将 Swift 与 Cocoa 和 Objective-C 一起使用中有一节是关于这一点的。-D
例如:
#if os(iOS)
let color = UIColor.redColor()
#else
let color = NSColor.redColor()
#endif
评论
INT_CONST
float
var floatVal = INT_CONST
Int
Float
floatVal
Int
是的,你可以做到。
在 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
评论
-D DEBUG
DEBUG=1
Apple LLVM 6.0 - Preprocessing
Preprocessor Macros
DEBUG=1
Preprocessor Macros
在许多情况下,您实际上并不需要条件编译;您只需要可以打开和关闭的条件行为。为此,您可以使用环境变量。这具有巨大的优势,即您实际上不必重新编译。
您可以在方案编辑器中设置环境变量,并轻松打开或关闭它:
可以使用 NSProcessInfo 检索环境变量:
let dic = NSProcessInfo.processInfo().environment
if dic["TRIPLE"] != nil {
// ... do secret stuff here ...
}
这是一个现实生活中的例子。我的应用仅在设备上运行,因为它使用模拟器上不存在的音乐库。那么,如何在模拟器上为我不拥有的设备拍摄屏幕截图呢?没有这些屏幕截图,我无法提交到 AppStore。
我需要假数据和不同的处理方式。我有两个环境变量:一个是打开时,它会告诉应用程序在我的设备上运行时从真实数据生成假数据;另一个,当打开时,在模拟器上运行时使用虚假数据(而不是丢失的音乐库)。由于 Scheme 编辑器中的环境变量复选框,可以轻松打开/关闭这些特殊模式。好处是,我不会在我的 App Store 版本中意外使用它们,因为存档没有环境变量。
评论
从 Swift 4.1 开始,如果你只需要检查代码是用调试还是发布配置构建的,你可以使用内置函数:
_isDebugAssertConfiguration()
(当 optimization 设置为-Onone
)在 Swift 3+ 上不可用)_isReleaseAssertConfiguration(
) (当优化设置为-O
时为 true)(_isFastAssertConfiguration()
(当 optimization 设置为-Ounchecked
)
例如
func obtain() -> AbstractThing {
if _isDebugAssertConfiguration() {
return DecoratedThingWithDebugInformation(Thing())
} else {
return Thing()
}
}
与预处理器宏相比,
- ✓ 无需定义自定义标志即可使用它
-D DEBUG
- ~ 它实际上是根据优化设置来定义的,而不是 Xcode 构建配置
✗ 未记录,这意味着可以在任何更新中删除该函数(但它应该是 AppStore 安全的,因为优化器会将这些转换为常量)
- 这些曾经被删除,但又回到了公众面前,缺乏
@testable
属性,未来 Swift 的命运不确定。
- 这些曾经被删除,但又回到了公众面前,缺乏
✗ 在 if/else 中使用将始终生成“永远不会执行”警告。
评论
if _isDebugAssertConfiguration()
if false
if true
如 Apple Docs 中所述
Swift 编译器不包括预处理器。相反,它利用编译时属性、生成配置和语言功能来实现相同的功能。因此,预处理器指令不会在 Swift 中导入。
我已经设法通过使用自定义构建配置实现了我想要的东西:
- 转到您的项目 / 选择您的目标 / 构建设置 / 搜索自定义标志
- 对于所选目标,使用 -D 前缀(不带空格)为 Debug 和 Release 设置自定义标志
- 对您拥有的每个目标执行上述步骤
以下是检查目标的方法:
#if BANANA
print("We have a banana")
#elseif MELONA
print("Melona")
#else
print("Kiwi")
#endif
使用 Swift 2.2 进行测试
评论
-DLOCAL
#if LOCAl #else #endif
#else
AppTarget
AppTargetLocal
#if LOCAL
#else
#if LOCAL
我对 Xcode 8 的两分钱:
a) 使用前缀的自定义标志工作正常,但是......-D
b) 使用更简单:
在 Xcode 8 中,有一个新部分:“活动编译条件”, 已经有两行,用于调试和发布。
只需添加您的定义 WITHOUT .-D
评论
-D
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/
评论
Xcode 8 及更高版本
使用“生成设置”/“Swift 编译器 - 自定义标志”中的“活动编译条件”设置。
- 这是用于将条件编译标志传递给 Swift 编译器的新构建设置。
- 只需添加如下标志:等。
ALPHA
BETA
然后用这样的编译条件检查它:
#if ALPHA
//
#elseif BETA
//
#else
//
#endif
提示:您也可以使用
#if !ALPHA
等
评论
基于活动编译条件的 isDebug 常量
另一个可能更简单的解决方案仍然会产生一个布尔值,你可以将它传递到函数中,而无需在整个代码库中加入条件,那就是定义为你的项目构建目标之一,并包含以下内容(我将其定义为全局常量):#if
DEBUG
Active 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 中使用不会生成“永远不会执行”警告。
这建立在 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 完全消除了函数调用。
评论
在构建设置中设置后,我更喜欢使用函数进行以下调用:DEBUG=1
GCC_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
编译器检查我的代码的语法,所以我确定它的语法是正确的并构建的。
![在 Xcode 8 及更高版本中,转到构建设置>搜索自定义标志 ]1
在代码中
#if Live
print("Live")
#else
print("debug")
#endif
评论
在使用 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 (在预处理器指令部分)
希望将来苹果能为他们的书写出更详细的内容和索引。
XCODE 9 及更高版本
#if DEVELOP
//print("Develop")
#elseif PRODUCTION
//print("Production")
#else
//
#endif
func inDebugBuilds(_ code: () -> Void) {
assert({ code(); return true }())
}
评论
@inlinable
func
code()
Moignans在这里的回答很好。这是另一条信息,以防万一,
#if DEBUG
let a = 2
#else
let a = 3
#endif
您可以像下面这样否定宏,
#if !RELEASE
let a = 2
#else
let 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 DEBUG
print("Debug mode")
#endif
Swift 5 更新了 Matt 的答案
let dic = ProcessInfo.processInfo.environment
if dic["TRIPLE"] != nil {
// ... do your secret stuff here ...
}
可以创建名为 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")
}
下一个:获取字符串的长度
评论