In C/C++/Objective C you can define a macro using compiler preprocessors. Moreover, you can include/exclude some parts of code using compiler preprocessors.
#ifdef DEBUG
// Debug-only code
#endif
Is there a similar solution in Swift?
Yes you can do it.
In Swift you can still use the "#if/#else/#endif" preprocessor macros (although more constrained), as per Apple docs. Here's an example:
#if DEBUG
let a = 2
#else
let a = 3
#endif
Now, you must set the "DEBUG" symbol elsewhere, though. Set it in the "Swift Compiler - Custom Flags" section, "Other Swift Flags" line. You add the DEBUG symbol with the -D DEBUG
entry.
As usual, you can set a different value when in Debug or when in Release.
I tested it in real code and it works; it doesn't seem to be recognized in a playground though.
You can read my original post here.
IMPORTANT NOTE: -DDEBUG=1
doesn't work. Only -D DEBUG
works. Seems compiler is ignoring a flag with a specific value.
As stated in Apple Docs
The Swift compiler does not include a preprocessor. Instead, it takes advantage of compile-time attributes, build configurations, and language features to accomplish the same functionality. For this reason, preprocessor directives are not imported in Swift.
I've managed to achieve what I wanted by using custom Build Configurations:
Go to your project / select your target / Build Settings / search for Custom Flags For your chosen target set your custom flag using -D prefix (without white spaces), for both Debug and Release Do above steps for every target you have
Here's how you check for target:
#if BANANA
print("We have a banana")
#elseif MELONA
print("Melona")
#else
print("Kiwi")
#endif
https://i.stack.imgur.com/zAKzJ.png
Tested using Swift 2.2
-DLOCAL
, on my #if LOCAl #else #endif
, it falls into the #else
section. I duplicated the original target AppTarget
and rename it to AppTargetLocal
& set its custom flag.
#if LOCAL
, the intended result when i run with the simulator and falls into #else
during testing. I want it to falls into #if LOCAL
as well during testing.
In many situations, you don't really need conditional compilation; you just need conditional behavior that you can switch on and off. For that, you can use an environment variable. This has the huge advantage that you don't actually have to recompile.
You can set the environment variable, and easily switch it on or off, in the scheme editor:
https://i.stack.imgur.com/opfbT.png
You can retrieve the environment variable with NSProcessInfo:
let dic = NSProcessInfo.processInfo().environment
if dic["TRIPLE"] != nil {
// ... do secret stuff here ...
}
Here's a real-life example. My app runs only on the device, because it uses the music library, which doesn't exist on the Simulator. How, then, to take screen shots on the Simulator for devices I don't own? Without those screen shots, I can't submit to the AppStore.
I need fake data and a different way of processing it. I have two environment variables: one which, when switched on, tells the app to generate the fake data from the real data while running on my device; the other which, when switched on, uses the fake data (not the missing music library) while running on the Simulator. Switching each of those special modes on / off is easy thanks to environment variable checkboxes in the Scheme editor. And the bonus is that I can't accidentally use them in my App Store build, because archiving has no environment variables.
A major change of ifdef
replacement came up with Xcode 8. i.e use of Active Compilation Conditions.
Refer to Building and Linking in Xcode 8 Release note.
New build settings
New setting: SWIFT_ACTIVE_COMPILATION_CONDITIONS
“Active Compilation Conditions” is a new build setting for passing conditional compilation flags to the Swift compiler.
Previously, we had to declare your conditional compilation flags under OTHER_SWIFT_FLAGS, remembering to prepend “-D” to the setting. For example, to conditionally compile with a MYFLAG value:
#if MYFLAG1
// stuff 1
#elseif MYFLAG2
// stuff 2
#else
// stuff 3
#endif
The value to add to the setting -DMYFLAG
Now we only need to pass the value MYFLAG to the new setting. Time to move all those conditional compilation values!
Please refer to below link for more Swift Build Settings feature in Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/
As of Swift 4.1, if all you need is just check whether the code is built with debug or release configuration, you may use the built-in functions:
_isDebugAssertConfiguration() (true when optimization is set to -Onone)
_isReleaseAssertConfiguration() (true when optimization is set to -O) (not available on Swift 3+)
_isFastAssertConfiguration() (true when optimization is set to -Ounchecked)
e.g.
func obtain() -> AbstractThing {
if _isDebugAssertConfiguration() {
return DecoratedThingWithDebugInformation(Thing())
} else {
return Thing()
}
}
Compared with preprocessor macros,
✓ You don't need to define a custom -D DEBUG flag to use it
~ It is actually defined in terms of optimization settings, not Xcode build configuration
✗ Undocumented, which means the function can be removed in any update (but it should be AppStore-safe since the optimizer will turn these into constants) these once removed, but brought back to public to lack of @testable attribute, fate uncertain on future Swift.
these once removed, but brought back to public to lack of @testable attribute, fate uncertain on future Swift.
✗ Using in if/else will always generate a "Will never be executed" warning.
if _isDebugAssertConfiguration()
will be evaluated to if false
in release mode and if true
is debug mode.
Xcode 8 and above
Use Active Compilation Conditions setting in Build settings / Swift compiler - Custom flags.
This is the new build setting for passing conditional compilation flags to the Swift compiler.
Simple add flags like this: ALPHA, BETA etc.
Then check it with compilation conditions like this:
#if ALPHA
//
#elseif BETA
//
#else
//
#endif
Tip: You can also use #if !ALPHA etc.
There is no Swift preprocessor. (For one thing, arbitrary code substitution breaks type- and memory-safety.)
Swift does include build-time configuration options, though, so you can conditionally include code for certain platforms or build styles or in response to flags you define with -D
compiler args. Unlike with C, though, a conditionally compiled section of your code must be syntactically complete. There's a section about this in Using Swift With Cocoa and Objective-C.
For example:
#if os(iOS)
let color = UIColor.redColor()
#else
let color = NSColor.redColor()
#endif
INT_CONST
anywhere a float
would be accepted. Swift would not allow this. Also, if you could do var floatVal = INT_CONST
inevitably it would breakdown somewhere later when the compiler expects an Int
but you use it as a Float
(type of floatVal
would be inferred as Int
). 10 casts later and its just cleaner to remove macros...
isDebug Constant Based on Active Compilation Conditions
Another, perhaps simpler, solution that still results in a boolean that you can pass into functions without peppering #if
conditionals throughout your codebase is to define DEBUG
as one of your project build target's Active Compilation Conditions
and include the following (I define it as a global constant):
#if DEBUG
let isDebug = true
#else
let isDebug = false
#endif
isDebug Constant Based on Compiler Optimization Settings
This concept builds on kennytm's answer
The main advantage when comparing against kennytm's, is that this does not rely on private or undocumented methods.
In 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
}()
Compared with preprocessor macros and kennytm's answer,
✓ You don't need to define a custom -D DEBUG flag to use it
~ It is actually defined in terms of optimization settings, not Xcode build configuration
✓ Documented, which means the function will follow normal API release/deprecation patterns.
✓ Using in if/else will not generate a "Will never be executed" warning.
My two cents for Xcode 8:
a) A custom flag using the -D
prefix works fine, but...
b) Simpler use:
In Xcode 8 there is a new section: "Active Compilation Conditions", already with two rows, for debug and release.
Simply add your define WITHOUT -D
.
-D
.
Moignans answer here works fine. Here is another piece of info in case it helps,
#if DEBUG
let a = 2
#else
let a = 3
#endif
You can negate the macros like below,
#if !RELEASE
let a = 2
#else
let a = 3
#endif
In Swift projects created with Xcode Version 9.4.1, Swift 4.1
#if DEBUG
#endif
works by default because in the Preprocessor Macros DEBUG=1 has already been set by Xcode.
So you can use #if DEBUG "out of box".
By the way, how to use the condition compilation blocks in general is written in Apple's book The Swift Programming Language 4.1 (the section Compiler Control Statements) and how to write the compile flags and what is counterpart of the C macros in Swift is written in another Apple's book Using Swift with Cocoa and Objective C (in the section Preprocessor Directives)
Hope in future Apple will write the more detailed contents and the indexes for their books.
XCODE 9 AND ABOVE
#if DEVELOP
//
#elseif PRODCTN
//
#else
//
#endif
There are some processors that take an argument and I listed them below. you can change the argument as you like:
#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 */
Also, You can use any custom flags like DEBUG
or any other flags you defined
#if DEBUG
print("Debug mode")
#endif
After setting DEBUG=1
in your GCC_PREPROCESSOR_DEFINITIONS
Build Settings I prefer using a function to make this calls:
func executeInProduction(_ block: () -> Void)
{
#if !DEBUG
block()
#endif
}
And then just enclose in this function any block that I want omitted in Debug builds:
executeInProduction {
Fabric.with([Crashlytics.self]) // Compiler checks this line even in Debug
}
The advantage when compared to:
#if !DEBUG
Fabric.with([Crashlytics.self]) // This is not checked, may not compile in non-Debug builds
#endif
Is that the compiler checks the syntax of my code, so I am sure that its syntax is correct and builds.
![In Xcode 8 & above go to build setting -> search for custom flags ]1
In code
#if Live
print("Live")
#else
print("debug")
#endif
func inDebugBuilds(_ code: () -> Void) {
assert({ code(); return true }())
}
@inlinable
in front of func
and this would be the most elegant and idiomatic way for Swift. In release builds your code()
block will be optimized and eliminated altogether. A similar function is used in Apple's own NIO framework.
This builds on Jon Willis's answer that relies upon assert, which only gets executed in Debug compilations:
func Log(_ str: String) {
assert(DebugLog(str))
}
func DebugLog(_ str: String) -> Bool {
print(str)
return true
}
My use case is for logging print statements. Here is a benchmark for Release version on 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)" )
prints:
Log: 0.0
Looks like Swift 4 completely eliminates the function call.
Swift 5 update for matt's answer
let dic = ProcessInfo.processInfo.environment
if dic["TRIPLE"] != nil {
// ... do your secret stuff here ...
}
Success story sharing
-D DEBUG
as stated above, you also need to defineDEBUG=1
inApple LLVM 6.0 - Preprocessing
->Preprocessor Macros
.-DDEBUG
from this answer: stackoverflow.com/a/24112024/747369.DEBUG=1
toPreprocessor Macros
, if you don't want to use it in Objective-C code.