#ifdef replacement in the Swift language
Solution 1
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.
Solution 2
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
Tested using Swift 2.2
Solution 3
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:
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.
Solution 4
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/
Solution 5
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
) -
(not available on Swift 3+)_isReleaseAssertConfiguration()
(true when optimization is set to-O
) -
_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
✗ Using in if/else will always generate a "Will never be executed" warning.
Comments
-
mxg over 2 years
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?
-
hansvb almost 10 years"For one thing, arbitrary code substitution breaks type- and memory-safety." Doesn't a pre-processor do its work before the compiler does (hence the name)? So all these checks could still take place.
-
Aleksandr Dubinsky almost 10 years@Thilo I think what it breaks is IDE support
-
Ephemera almost 10 yearsI think what @rickster is getting at is that C Preprocessor macros have no understanding of type and their presence would break Swift's type requirements. The reason macros work in C is because C allows implicit type conversion, which means you could put your
INT_CONST
anywhere afloat
would be accepted. Swift would not allow this. Also, if you could dovar floatVal = INT_CONST
inevitably it would breakdown somewhere later when the compiler expects anInt
but you use it as aFloat
(type offloatVal
would be inferred asInt
). 10 casts later and its just cleaner to remove macros... -
Charles Harley almost 10 yearsThis is the correct answer, although it should be noted that you can only check for the presence of the flag but not a specific value.
-
Eugene almost 10 yearsFor some reason my environment variable returned as nil on the second app launch
-
Maury Markowitz about 9 yearsI'm trying to use this but it doesn't seem to work, it's still compiling the Mac code on iOS builds. Is there another setup screen somewhere that has to be tweaked?
-
Lloyd Sargent about 9 yearsWell, that is unpleasant. I frequently use #if 0/#else/#endif to try test code. It makes it trivial to go back and forth to test things like durability or speed. I understand the preprocessor was just another hack (on a hack on a hack). But in this case it really was a Useful Thing®
-
MLQ about 9 yearsAdditional note: On top of adding
-D DEBUG
as stated above, you also need to defineDEBUG=1
inApple LLVM 6.0 - Preprocessing
->Preprocessor Macros
. -
Sunkas almost 9 yearsCan this be used to make sure some code (debug-login-credentials) are not build into the released app when building with different configurations?
-
Eric almost 9 yearsWatch out: Environment Variables are set for all build configurations, they can't be set for individual ones. So this is not a viable solution if you need the behaviour to change depending on whether it's a release or a debug build.
-
matt almost 9 years@Eric Agreed, but they are not set for all scheme actions. So you could do one thing on build-and-run and a different thing on archive, which is often the real-life distinction you want to draw. Or you could have multiple schemes, which also a real-life common pattern. Plus, as I said in my answer, switching environment variables on and off in a scheme is easy.
-
Eric almost 9 years@matt: I see. But how do you set an environmental variable for the Archive scheme action? I don't see an Arguments tab there... (Xcode 6.3.2)
-
matt almost 9 years@Eric added a real-life example (actually came up yesterday) to my answer.
-
Zmey almost 9 years@LloydSargent, "#if false" can be used in place of "#if 0"
-
Dan Rosenstark almost 9 years@Eric but one can create different schemes (e.g., two or more per target) with different environmental variables, right?
-
Kramer almost 9 yearsI couldn't get this to work until I changed the formatting to
-DDEBUG
from this answer: stackoverflow.com/a/24112024/747369. -
John Bushnell over 8 yearsI'm experiencing a problem with this solution, or maybe a quirk. It works when I'm messing around before release. I can set my scheme to compile for either debug or release and the #if DEBUG yields the correct result. BUT, when I archive and submit the app to the App Store, it's appearing on the store in debug mode, and this is despite the fact my Archive build configuration is set to Release, which I've confirmed. I think this may not be working out on the store as people think it is. Anyone else confirm this?
-
iupchris10 over 8 yearsEnvironment variables do NOT work in archive mode. They are only applied when the app is launched from XCode. If you try to access these on a device, the app will crash. Found out the hard way.
-
matt over 8 years@iupchris10 "Archiving has no environment variables" are the last words of my answer, above. That, as I say in my answer, is good. It's the point.
-
Valentin Shergin over 8 yearsIf you are using
.xcconfig
files you can set up these macro like this:OTHER_SWIFT_FLAGS = $(inherited) "-D" "MAC_APP_STORE"
. -
derpoliuk over 8 years@MattQuiros There's no need to add
DEBUG=1
toPreprocessor Macros
, if you don't want to use it in Objective-C code. -
ma11hew28 about 8 yearsAre these built-in functions evaluated at compile time or runtime?
-
kennytm about 8 years@MattDiPasquale Optimization time.
if _isDebugAssertConfiguration()
will be evaluated toif false
in release mode andif true
is debug mode. -
John Bushnell about 8 years@BigRon I ended up setting a boolean in my highest level framework from my main project (where the #if DEBUG is reliable), and then all of my code within my frameworks check the boolean instead of using #if DEBUG. Of course this means the other code pathways are all still there in release, but just never execute. I haven't found a solution beyond this, but I haven't been checking lately to see if Apple may have done anything to correct it.
-
BigRon about 8 years@John A few days ago I implemented this Debug solution without your suggestion. I'll let you know how it turns out.
-
finneycanhelp about 8 yearsThis worked in our app target but not the ...AppTests even though we added them to
Apple LLVM 6.0 - Preprocessing
->Preprocessor Macros
as well as "Swift Compiler - Custom Flags" -
Franklin Yu about 8 yearsI can't use these functions to opt out some debug-only variable in release, though.
-
Daniel about 8 yearsIs there also something like
#ifndef DEBUG
? (if not debug...) -
Jean Le Moignan about 8 years@Daniel You can use standard boolean operators (ex: ` #if !DEBUG ` )
-
c0ming about 8 years1.with white space work also, 2.should set the flag only for Debug?
-
Srivathsalachary Vangeepuram almost 8 yearsAre these functions documented somewhere?
-
Crashalot almost 8 yearsis the formatting
-D DEBUG
(with space) or-DDEBUG
(no space)? if you also need to define DEBUG=1 as @MattQuiros said, shouldn't the answer be updated? -
Crashalot almost 8 years@MattQuiros it seems to work without defining
DEBUG=1
inPreprocessor Macros
? are you sure this is still the case? -
tcurdt almost 8 years@Thilo you are correct - a pre-processor does not break any type or memory safety.
-
tcurdt almost 8 yearsYou can think of it as a fancy search-and-replace/templating before the compilation step. Given it's a source level transformation it only breaks things if the result of the transformation breaks things. Which is why @Ephemera's example and conclusion is not correct like that.
-
kennytm almost 8 years@TomHarrington It wasn't publicly documented anywhere. The source code is in github.com/apple/swift/blob/master/stdlib/public/core/…. There is a Draft SE proposal to replace these functions by a better syntax like
#if config(debug)
. -
cdf1982 almost 8 years@c0ming it depends on your needs, but if you want something to happen only in debug mode, and not in release, you need to remove -DDEBUG from Release.
-
Perwyl Liu almost 8 yearsAfter i set the custom flag
-DLOCAL
, on my#if LOCAl #else #endif
, it falls into the#else
section. I duplicated the original targetAppTarget
and rename it toAppTargetLocal
& set its custom flag. -
Andrej almost 8 years@PerwylLiu Yes, in that way you can actually set different flags for different targets, if you have more than one. Just make sure to not forghet to set the flags for other targets.
-
Perwyl Liu almost 8 years@Andrej do you happen to know how to make XCTest recognise the custom flags as well? I realise it falls into
#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. -
ff10 almost 8 yearsTo be very clear about this flag: the naming DEBUG is conventional, not something that is predefined by Xcode or the preprocessor. Set -DDEBUG for the Debug configuration in "other Swift flags". Set something else, e.g. -DRELEASE for the Release configuration.
-
miken.mkndev over 7 yearsThis should be the accepted answer. The current accepted answer is incorrect for Swift as it only applies to Objective-C.
-
J. Cocoe over 7 yearsThe no-space version (
-DDEBUG
) works for me, but the missing piece to this trick is that you need to add this in the "Swift Compiler - Custom Flags" section of the Target, not the section of this name of the Project settings. -
harmeet07 over 7 yearsIt's -D Debug for Objective C projects and D- Debug for Swift projects.
-
Warpzit over 7 yearsRemember this needs to be defined for the specific framework/extension that use it! So if you have a keyboard/today extension define it there. If you have some other kind of framework same thing. This might only be necessary if the main target is objective c...
-
CodeBender over 7 yearsAs of Swift 3.0 & XCode 8, these functions are invalid.
-
Yitzchak over 7 yearsThanks for mentioning that there are TWO ROWS FOR DEBUG AND RELEASE
-
Glenn Posadas over 7 yearsanyone tested this in release?
-
kennytm almost 7 years@CodeBender On Swift 3.1 only
_isReleaseAssertConfiguration()
is invalid, the other two becomes public again due to a bug. -
Stan almost 7 yearsThis is exactly the correct solution for the XCTest case, where you want a default behavior when the application is running in the simulator, but you want to strictly control the behavior in the tests.
-
user1046037 over 6 years@Andrej could you edit the answer to use the
Active Compilation Conditions
instead ofOther Swift Flags
. Now there is more granular control based on the configuration -
user1046037 over 6 yearsAlso no need to prefix
D
if you useActive Compilation Conditions
-
Jonny over 6 yearsIs there anyway to disable a set Active Compilation Conditions at build time ? I need to disable the DEBUG condition when building the debug configuration for testing.
-
Stunner over 6 yearsAs of Xcode 9 GM I didn't have to add any flags to the build settings as I saw that
DEBUG
was already included underActive Compilation Conditions
underSwift Compiler-Custom Flags
. However, I am not sure if it is like this by default (when you create a new project) or was placed there by CocoaPods. -
matthias over 6 years@Jonny The only way I've found is to create a 3rd build config for the project. From the Project > Info tab > Configurations, hit '+', then duplicate Debug. You can then customize the Active Compilation Conditions for this config. Don't forget to edit your Target > Test schemes to use the new build configuration!
-
Mani over 6 yearsThis is the updated answer for swift users. ie without
-D
. -
Benjamin over 6 years@Stunner It's not placed by CocoaPods. I'm using Carthage and the flag is here. Seems to be the default as of Xcode 9.
-
Andrej over 6 yearsRelated to @iupchris10 comment - I'd like to clarify that in your project you can leave the code that tries to access the environment variables, even if you archive the app. When you're running an app that has been archived you get
nil
for the expectedString?
. So if you deal properly with the optional result it wont crash your app. Here's the Xcode9 snippet:let myCustomVar = ProcessInfo.processInfo.environment["MY_CUSTOM_VAR"]
.myCustomVar
. -
matt over 6 years@Andrej I think that's what my answer says (illustrating with
if dic["TRIPLE"] != nil
) but thanks for underlining the point. -
Swati about 6 yearshow do you use the value for a since it is defined inside the block
-
shokaveli about 6 yearsThis should be the correct answer..its the only thing that worked for me on xCode 9 using Swift 4.x !
-
Joney Spark about 6 yearsEliminates, as in removes the call in its entirety when not in debug - due to the function being empty? That would be perfect.
-
Denis Kutlubaev almost 6 yearsBTW, In Xcode 9.3 Swift 4.1 DEBUG is already there in Active Compilation Conditions and you don't have to add anything to check for DEBUG configuration. Just #if DEBUG and #endif.
-
Motti Shneor almost 6 yearsI think this is both off-topic, and a bad thing to do. you do not want to disable Active Compilation Conditions. you need a new and different configuration for testing - that will NOT have the "Debug" tag on it. Learn about schemes.
-
Motti Shneor almost 6 years@kennytm Do you know if these functions are still available as of Swift 4, and newer? can you please update your answer?
-
kennytm almost 6 years@MottiShneor Still exists on 4.1. Updated.
-
Motti Shneor almost 6 yearsThese are NOT preprocessor definitions, and Swift has no preprocessor. These are compile-time attributes, and build-configuration attributes.
-
Stephan Boner almost 6 yearsHow can I test if it works? In Objective-C I used to write sth into the code that it gives a compiler error when it's in the right section but here this doesn't work anymore
-
Benny Davidovitz over 5 yearsextension ProcessInfo{ var isDebug : Bool{ get{ return self.environment["DEBUG"] == "1" } } }
-
Shayne over 5 yearsThis isn't conditional compilation. While useful , its just a plain old runtime conditional. The OP is asking after compiletime for metaprogramming purposes
-
nickgzzjr about 5 yearsThis seems to be the only solution that is working on Xcode 10.1 Swift 4.2!
-
user924 almost 5 yearsbut it's a very ugly solution, it's better to use
if _isDebugAssertConfiguration() { ... }
, this one is more similar to Andorid'sif (BuildConfig.DEBUG) { ... }
-
mojuba almost 5 yearsJust add
@inlinable
in front offunc
and this would be the most elegant and idiomatic way for Swift. In release builds yourcode()
block will be optimized and eliminated altogether. A similar function is used in Apple's own NIO framework. -
Dale almost 5 yearsYou have hit on it here! Swift #if looks at custom flags NOT preprocessor macros. Please update your answer with the content from the link, often times links will break after a while
-
Peter Schorn over 3 yearsSwift actually has a compiler flag you can use to see if the app is running on a simulator:
#if targetEnvironment(simulator)
-
matt over 3 years@PeterSchorn Correct. My answer says that sometimes that's not what's needed. What you're saying is true and what I'm saying is true. Ain't life grand?
-
Peter Schorn over 3 years@matt I didn't mean to imply that my answer contradicted anything you said. I do agree with everything you said. My answer is purely additive. I just didn't see this compiler flag mentioned anywhere else, so I decided post a comment about it.
-
bshirley over 3 yearsApple Docs link above is dead, currently this will get you to one (not sure if it's the same one) docs.swift.org/swift-book/ReferenceManual/Statements.html
-
Nguyễn Anh Tuấn over 3 yearsI had tried to set the flag in "Other Swift Flags" but nothing happened. Thanks for your suggestion to set it in "Active Compilation Conditions". It works.
-
Alex Moore over 3 yearsNot enough people realize the efficiencies and workflow ramifications of adding a million re-compilation requirements... thank you for this @matt
-
Dennis CM over 2 years@matthias That's exactly what I was looking for. Thank you.
-
Cliff Ribaudo about 2 yearsSee this documentation for a full list of the conditions and their use: docs.swift.org/swift-book/ReferenceManual/Statements.html#