In absence of preprocessor macros, is there a way to define practical scheme specific flags at project level in Xcode project - ios

Before swift I would define a set of schemes for alpha, beta, and distribution builds. Each of these schemes would have a set of macros that were defined to gate certain behaviors at the project level. The simplest example is the DEBUG=1 macro that is defined by default for all Xcode projects in the default scheme for the Run build. One could query #ifdef DEBUG ... and make decisions in the code accordingly, even compiling out non-necessary code.
It seems that this type of configurational gating is not as easy using swift, as macros are not supported. Can someone suggest a comparable approach, I don't care if the code is compiled out, per se. I would like to gate features based on build scheme, though.

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.
(Build Settings -> Swift Compiler - Custom Flags)
As usual, you can set a different value when in Debug or when in Release.
I tested it in real code; it doesn't seem to be recognized in a playground.

We ran into an issue with not wanting to set swift compiler flags because we didn't want to have to set them and keep them up to date for different targets etc. Also, in our mixed codebase, we didn't want to make remember to set our flags appropriately all the time for each language.
For ours, we declared a file in ObjC
PreProcessorMacros.h
extern BOOL const DEBUG_BUILD;
In the .m
PreProcessorMacros.m
#ifdef DEBUG
BOOL const DEBUG_BUILD = YES;
#else
BOOL const DEBUG_BUILD = NO;
#endif
Then, in your Objective-C Bridging Header
#import "PreProcessorMacros.h"
Now, use this in your Swift codebase
if DEBUG_BUILD {
println("debug")
} else {
println("release")
}
This is definitely a workaround, but it solved our problem so I posted it here in the hopes that it will help. It is not meant to suggest that the existing answers are invalid.

More swifty solution to Logans method. Set -D DEBUG in Other Swift Flags of Swift Compiler - Custom Flags section in build settings of your target.
Then declare following method in global scope:
#if DEBUG
let isDebugMode = true
#else
let isDebugMode = false
#endif
Now use it as
if isDebugMode {
// Do debug stuff
}

For me, set the debug item of "Active Compilation Condition" to "DEBUG" worked.
Then using DEBGU key work in #IF DEBUG works in debug mode and #ELSE in release mode:
Select your target,
In Build Setting tab search for "Active Compilation Condition",
Set the value of its "Debug" item to "YourKeyWord",
Use simply as follow:
#if DEBUG
print("You'r running in DEBUG mode!")
#else
print("You'r running in RELEASE mode!")
#endif

Swift compiler directives
You can use next compiler directive
#if <some_key>
//logic 1
#else
//logic 2
#endif
//pre Xcode v8
Other Swift Flags(OTHER_SWIFT_FLAGS) = -D <some_key>
-D DEBUG
//from Xcode v8
Active Compilation Conditions(SWIFT_ACTIVE_COMPILATION_CONDITIONS) = <some_key>
DEBUG

I'm working in a mixed language code base where the obj-c code uses a macro to send debug messages to the console (and that macro relies on our debug preprocessor flag). I wanted to be able to call that same macro in the swift code...
I created a class method on one of my obj-c classes that is a wrapper around that macro.
I added that obj-c header to our bridge header file.
Now my swift code calls that class method as a "proxy" to the obj-c macro.
It's mildly annoying that I can't just call the macro straight up in the swift code, but at least now I only have one place in the project to worry about turning my debug flag on/off.

Related

How can I differentiate between multiple targets in xcode at runtime

Working on an old objective c application where in I need to create multiple targets. Question is how do I differentiate between multiple targets run time in the code and accordingly I need to load the resources from bundle.
Project > Build Settings > Preprocessor Macros
define there different macros for different targets e.g.:
TARGET_1
TARGET_2
and in code you can diferenciate it like this:
NSString *pathToMyResource = nil;
#ifdef TARGET_1
pathToMyResource = #"pathToMyResourceForTarget1";
#else
pathToMyResource = #"pathToMyResourceForTarget2";
#endif
EDIT: added swift syntax
#if DEBUG
let apiKey = "KEY_A"
#else
let apiKey = "KEY_B"
#endif
see here: Swift 3: how to use PREPROCESSOR Flags (like `#if DEBUG`) to implement API keys?
You can use #matloob's answer. Below is an another approach.
You can also use Preprocessing for differentiating among targets.
Please have a look at following tutorial. This may also help you.
Reference :
Target Differentiation dynamically - Appcoda

how to conditionally compile swift according to the branch [duplicate]

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
Tested using Swift 2.2
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.
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.
✗ Using in if/else will always generate a "Will never be executed" warning.
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
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.
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
//print("Develop")
#elseif PRODUCTION
//print("Production")
#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 }())
}
Source
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 ...
}

Access Environment Variables from Swift [duplicate]

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
Tested using Swift 2.2
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.
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.
✗ Using in if/else will always generate a "Will never be executed" warning.
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
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.
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
//print("Develop")
#elseif PRODUCTION
//print("Production")
#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 }())
}
Source
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 ...
}

Swift: iOS Deployment Target Command Line Flag

How do I check the iOS deployment target in a Swift conditional compilation statement?
I've tried the following:
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
// some code here
#else
// other code here
#endif
But, the first expression causes the compile error:
Expected '&&' or '||' expression
TL;DR? > Go to 3. Solution
1. Preprocessing in Swift
According to Apple documentation on preprocessing directives:
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.
That is why you have an error when trying to use __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 which is a C preprocessing directive. With swift you just can't use #if with operators such as <. All you can do is:
#if [build configuration]
or with conditionals:
#if [build configuration] && ![build configuration]
2. Conditional compiling
Again from the same documentation:
Build configurations include the literal true and false values,
command line flags, and the platform-testing functions listed in the
table below. You can specify command line flags using -D <#flag#>.
true and false: Won't help us
platform-testing functions: os(iOS) or arch(arm64) > won't help you, searched a bit, can't figure where they are defined. (in compiler itself maybe?)
command line flags: Here we go, that's the only option left that you can use...
3. Solution
Feels a bit like a workaround, but does the job:
Now for example, you can use #if iOSVersionMinRequired7 instead of __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0, assuming, of course that your target is iOS7.
That basically is the same than changing your iOS deployment target version in your project, just less convenient...
Of course you can to Multiple Build configurations with related schemes depending on your iOS versions targets.
Apple will surely improve this, maybe with some built in function like os()...
Tested in Swift 2.2
By saying Deployment Target you mean iOS version, or App Target? Below I'm providing the solution if you have multiple versions of the app (free app, payed app, ...), so that you use different App Targets.
You can set custom Build configurations:
1. go to your project / select your target / Build Settings / search for Custom Flags
2. for your chosen target set your custom flag using -D prefix (without white spaces), for both Debug and Release
3. do above steps for every target you have
To differentiate between targets you can do something like this:
var target: String {
var _target = ""
#if BANANA
_target = "Banana"
#elseif MELONA
_target = "Melona"
#else
_target = "Kiwi"
#endif
return _target
}
override func viewDidLoad() {
super.viewDidLoad()
print("Hello, this is target: \(target)"
}
I have come across this issue recently when building a library whose source code supports multiple iOS and macOS versions with different functionality.
My solution is using custom build flags in Xcode which derive their value from the actual deployment target:
TARGET_IOS_MAJOR = TARGET_IOS_MAJOR_$(IPHONEOS_DEPLOYMENT_TARGET:base)
TARGET_MACOS_MAJOR = TARGET_MACOS_MAJOR_$(MACOSX_DEPLOYMENT_TARGET:base)
Referring to those user defined settings in Others Swift Flags like:
OTHER_SWIFT_FLAGS = -D$(TARGET_MACOS_MAJOR) -D$(TARGET_IOS_MAJOR)
allows me to check for the actual major OS version in my Swift sources as follows:
#if os(macOS)
#if TARGET_MACOS_MAJOR_12
#warning("TARGET_MACOS_MAJOR_12")
#elseif TARGET_MACOS_MAJOR_11
// use custom implementation
#warning("TARGET_MACOS_MAJOR_11")
#endif
#elseif os(iOS)
#if TARGET_IOS_MAJOR_15
#warning("TARGET_IOS_MAJOR_15")
#elseif TARGET_IOS_MAJOR_14
#warning("TARGET_IOS_MAJOR_14")
#else
#warning("older iOS")
#endif
#endif
Currently I don't know if a similar approach would be possible in a SPM package. This is something I will try to do in a later phase.
You can't do it in a conditional compilation statement like that. "Complex macros" as Apple calls them are not supported in Swift. Generics and types do the same thing, in their mind, with better results. (Here's a link they published https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_13)
Here's a function I came up with that accomplishes the same thing (and obviously just replace the string returns with whatever is useful for you like a boolean or just the option itself):
func checkVersion(ref : String) -> String {
let sys = UIDevice.currentDevice().systemVersion
switch sys.compare(ref, options: NSStringCompareOptions.NumericSearch, range: nil, locale: nil) {
case .OrderedAscending:
return ("\(ref) is greater than \(sys)")
case .OrderedDescending:
return ("\(ref) is less than \(sys)")
case .OrderedSame:
return ("\(ref) is the same as \(sys)")
}
}
// Usage
checkVersion("7.0") // Gives "7.0 is less than 8.0"
checkVersion("8.0") // Gives "8.0 is the same as 8.0"
checkVersion("8.2.5") // Gives "8.2.5 is greater than 8.0"
I know your question is been here for a while but just in case someone's still looking for an answer they should know that starting with Swift 2.0 you can do something like this:
if #available(iOS 8, *) {
// iOS 8+ code
} else {
// older iOS code
}
You can read more about it here.

How to know if NSAssert is disabled in release builds?

I often saw "assert " in iOS code, I google it, and got to know it assert true or false.
I want to know if this will auto disable in release mode?
Update: Verified this works in Xcode 8 as well.
In Xcode 7, go into the project build settings and search for "Assert" in the search bar. This shows section "Apple LLVM 7.0 - Preprocessing" section. There is a setting named "Enable Foundation Assertions".
I have successfully enabled/disabled NSAssert from there.
Use NSAssert() and its companions.
in the project define NS_BLOCK_ASSERTIONS for your release configuration.
Xcode 4 tremplates disable NSAsserts in the release configuration. It adds
-DNS_BLOCK_ASSERTIONS=1
to "Other C Flags" for "Release".
From the documentation:
Assertions are disabled if the preprocessor macro NS_BLOCK_ASSERTIONS is defined.
The NSAssert macro evaluates the condition and serves as a front end to the assertion handler.
Each thread has its own assertion handler, which is an object of class NSAssertionHandler. When invoked, an assertion handler prints an error message that includes the method and class names (or the function name). It then raises an NSInternalInconsistencyException exception. If condition evaluates to NO, the macro invokes handleFailureInMethod:object:file:lineNumber:description: on the assertion handler for the current thread, passing desc as the description string.
This macro should be used only within Objective-C methods.
I will here provide a meta-answer:
Both #CocoaFu and #dasblinkenlight are correct. NS_BLOCK_ASSERTIONS turns off NSAssert() and NDEBUG turns off assert(). You need both if you use both.
As Zaph said -DNS_BLOCK_ASSERTIONS=1 is set for release. However, if you wanted to check this.
First observe in the docs that NSAssert is disabled by the macro NS_BLOCK_ASSERTIONS. Then add this to the build and observe it complies ok:
#ifdef NS_BLOCK_ASSERTIONS
#error Error - NS_BLOCK_ASSERTIONS is defined
#endif
Then change the scheme to release (cmd - shift - <)
Then observe that the build fails. Therefore NS_BLOCK_ASSERTIONS is defined meaning NSAsserts are disabled.
Asserts are conditionally compiled out of your code when NDEBUG is defined. If you define NDEBUG=1 in the corresponding build settings section, you will deactivate asserts in your code regardless of the release or debug mode.
Here's what I do at the top of my main():
#if defined(NDEBUG)
{
// The assertion code below should be compiled out of existence in a release
// build. Log an error and abort the program if it is not.
bool ok = true;
NSCAssert(ok = false, #"NS assertions should be disabled but are not");
if (!ok)
{
NSLog(#"Detected release build but NS_BLOCK_ASSERTIONS is not defined");
return -1;
}
}
#endif
Note that since main() is a C function and not an Objective-C function, NSCAssert is used above rather than NSAssert. (NSAssert expects self to be valid.)
Now, as of Xcode 6, the setting is ENABLE_NS_ASSERTIONS, which is set to 1 for Debug configurations and 0 for Release, by default.
You can opt into it for Release builds on the command line by passing the ENABLE_NS_ASSERTIONS=1 argument, which I'm doing for running unit tests that check for assertion conditions, but otherwise should run with the DEBUG flag off.

Resources