In Objective-C I had a bunch of compiler flags set in Build Settings -> Other C Flags that were being used in the code. For instance:
Flag => -DPortNumber = 1
And in code I was able to access it by #(PortNumber)
This doesn't work in Swift, and I'm not able to find an answer.
The -D flag to C compilers defines a preprocessor macro. There are no preprocessor macros in Swift. So if you're looking to do something like:
// compile with -DPORT_NUMBER 31337
var port = PORT_NUMBER // error
... you can't. Swift is designed for source code to be syntactically complete before compilation. If you could switch out blocks of it at build time, you'd break the ability of the toolchain to help verify that your code is correct. (Partly this is because preprocessor macros in C are textual replacement: you can use them to rewrite any part of the language, not just fill in values for variables.)
The Swift compiler does have a -D flag, but its use is more limited: you can use it for build configurations only. So, if you wanted to do something like the following, you'd be cool:
// compile with -DUSE_STAGING_SERVER
#if USE_STAGING_SERVER
var port = 31337
#else
var port = 80
#endif
Note that unlike C, everything inside an #if block needs to be syntactically complete. (For example, you can't put just the declaration line of a func in an #if block and leave the function body outside the conditional.)
Of course, this doesn't help you if you want to have a configuration value set at compile time be used in your code. For that, I'd recommend alternate approaches. Xcode can still do textual substitution in resource files, like property lists. (Note that the Info.plist that comes with your app is full of things like $(TARGET_NAME), for example.) So, you could include a bundle resource with your app whose contents are populated at compile time according to your project settings, then read your port number from that.
Related
After reading this stackoverflow post, I tried to introduce macros in my project.
I have the following code in a sample macOS CommandLine tool.
#if ELDIABLO
NSLog ("ELDIABLO macro detected!")
#else
NSLog ("ELDIABLO macro not detected!")
#endif
The ELDIABLO macro is declared in Target->BuildSettings->SwiftCompiler/OtherSwiftFlags (prefixed with -D).
This works.
SwiftMacros[73110:12048088] Detected ELDIABLO macro!!
Now, when I transferred the same concept to my original project it doesn't work. I always get
ELDIABLO macro not detected!
According to another stackoverflow post, the macros should be defined in Target->BuildSettings->SwiftCompiler/ActiveCompilationConditions (without -D prefix).
I tried that too, but didn't work.
What's wrong here? What am I missing?
I'm using Xcode 13.4.
My project structure: One target (the app) dependent on many other targets (static libs). All macro settings are applied to the app target (not to the static libs).
I was setting the macro in the app target, but the code which uses macro is one of the other targets i.e static libs, though the static libs targets are added as a dependency for the app target.
After adding the macro to the static lib target, it works.
That means the macro didn't find your "Condition setting", ex: ELDIABLO
So now you need to open your Project/[Your in-use target]/Build Settings.
Then, search the setting with the keyword: "active compilation". And, put your CONDITION_CONFIG here. One line for one CONDITION_CONFIG.
Check image below:
In Objective-C we could add a C Flag of -DVAR_NAME=#\"string value\" and then get the value with NSString *var = VAR_NAME.
How do we do this in Swift?
An example of this would be defining an api host based on the current git branch. Say the branch is feature1 and it should connected to https://feature1.example.com. A shell script can easily find the current branch and add the C Flag. Then in Objective-C we can use the branch from the shell script to generate the api host url.
Update
I am not asking about boolean flags. I need to pass a string value.
Update 2
So far all I can come up with is to use a build script to generate a swift class.
Update 3
Another option is to add custom keys to the Info.plist. This isn't always an acceptable solution but for configuration values this works.
Macros are evil. They have been abused for things like this and it's not a clean solution. You have the following options (some of them already mentioned by you).
Info.plist
The most simple, easy to read and edit. Not good for big configurations.
Your own .plist.
Easy to read and edit (even from command line tools before the build, see PlistBuddy tool). Better for bigger configurations.
Auto generated code
Your code can contain an expression that can be matched easily, e.g.
let myConfigValue = "${MY-CONFIG-VALUE}".
You can replace those values easily using command line tools, e.g. sed. This basically replicates macros without using C preprocessor. This is not very clean but usable if you already have a build system that you don't want to change.
Multiple code variants
You can have a Swift file with configuration constants and use #if to switch between them.
Define a condition like this:
var window: UIWindow?
#if MYDEF
var textureRenderThread : TextureRenderThread?
#endif
In the Project->Build Settings->Swift Compiler->Custom Flags
Set "-D MYDEF" as the value for "Other Swift Flags"
I came across this unusual code in a tutorial I was using.
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
return CGFloat(sqrtf(Float(a)))
}
#endif
It looks nothing like the code I've been learning so far. I know it's a square root function but the hashtag? Something about my computer architecture?
Please explain it to me in simple terms.
#if condition
// Code in here
#endif
This is a conditional compilation directive - it is used to hide blocks of code from the compiler. The code in the block is only compiled if the condition is true.
It's supported in many languages, notably C and C++. It is often used to account for processor architecture and operating system differences - allowing one code-base to compile on many different platforms.
It can also be used to remove debugging/tracing code in a release build.
The condition is evaluated once at compile time, normally in an initial pass over the source code before the main compiler.
You can set those kind of conditional compilation directive for various purpose.
For example you might have an environnement for DEBUG and one for RELEASE, depending on which you wan't to compile your might not use the same functions / values.
In you case, #if !(arch(x86_64) || arch(arm64)) is to determine the architecture of your device (or simulator).
Indeed, some iDevice run 32bits and others 64bits (5S and newer). Float aren't represented the same way.
As #Martin wrote, you might not have to use this code anymore :)
I have added some debugging code to my app which I want to call only when needed. I remember there is some kind of IFDEF that can be used to conditionally include code into a source file.
For example I might have something like this:
IFDEF kDebugEnabled == YES {
// some debugging code here
}
And then this piece of code is only compiled into the binary if that kDebugEnabled is YES.
How can I do something like this?
Please note: I don't want to use the project compiler flag settings. I just want to define a BOOL (or something that serves the purpose just as well) which is true or false and then just easily set it in my App Delegate for example. I find it hard to navigate to the project compiler settings, searching for a flag and then setting it. I know there is a Debug flag which might be of use.
What you are looking for is:
#ifdef __YOURSYMBOL__
<conditional code>
#endif
You can programmatically define __YOURSYMBOL__ like this:
#define __YOURSYMBOL__
__YOURSYMBOL__ can be any string that makes sense to you to remember why you are including/excluding that code snippet.
The DEBUG constant is a special preprocessor constant that the compiler defines specifically for you when the code is built for debugging, so you can simply use it:
#ifdef DEBUG
<conditional code>
#endif
Take into account that this is the C-preprocessor, not C, nor Objective-C that you are using, so a test like kDebugEnabled == YES (where kDebugEnabled is an Objective-C variable) is simply not possible. You can define integer values for your constants, like this:
#define __LOG_LEVEL__ 3
and then test for it:
#if __LOG_LEVEL__ == 3
...
Endif
As far as I know, you can't have code in your classes that is not compiled into the final product without using compiler flags. However, using the DEBUG flag is a lot easier than you think. If you are using Xcode 4, it's set up for you by default.
#ifdef DEBUG
// Your debug-only code goes here
#endif // DEBUG
Xcode has, by default, two configurations, Debug and Release. When you use the debug build configuration, among other things, it sets the DEBUG compiler flag, which you can then use to conditionally compile code. No need to mess with compilation settings at all.
Is it generally valid that a macro is used to create a method name? I mean...actually it's just simple text replacement before the compiler actually runs, right?
Yes, it is valid; macro expansion occurs before the compiler even reads the code. The main limitation is that one cannot embed a preprocessor directive within a preprocessor directive. So, for example:
// This is ok:
#define PREFIX(X) this_name_is_prefixed_ ## X
// ...
- (void) PREFIX(doSomething):id;
// ...
// But this isn't:
#define IMPORT(X) #import X
IMPORT(<Foundation/Foundation.h>) // <= Don't expect this to work
With the exception of the "#import" directive, Objective-C's preprocessor is basically the same as the C preprocessor. (The "#import" is like "#include", except that #import implies include only once, so preprocessor guards are not required for headers that are included only with #import).
There is nothing that prevents that. It is even commonly used (though I don't know for the iphone), for instance, in device drivers implementation. In that case, macros are used to generate boilerplate code, and for this boilerplate to communicate with your code, you have to either guess the correct function names (not advised), or use generating macros, for example USB_ATTACH(uthum) to generate the signature of the attach method for the uthum driver.