Which conditional compile to use to switch between Mac and iPhone specific code? - iphone

I am working on a project that includes a Mac application and an iPad application that share code. How can I use conditional compile switches to exclude Mac-specific code from the iPhone project and vice-versa? I've noticed that TARGET_OS_IPHONE and TARGET_OS_MAC are both 1, and so they are both always true. Is there another switch I can use that will only return true when compiling for a specific target?
For the most part, I've gotten the files to cooperate by moving #include <UIKit/UIKit.h> and #include <Cocoa/Cocoa.h> into the precompile headers for the two projects. I'm sharing models and some utility code that fetches data from RSS feeds and Evernote.
In particular, the [NSData dataWithContentsOfURL:options:error:] function takes a different constant for the options parameter iOS 3.2 and earlier and Mac OS 10.5 and earlier than it does for iOS 4 and Mac OS 10.6. The conditional I'm using is:
#if (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_3_2)) || (TARGET_OS_MAC && (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5))
This seems to work, but I want to make sure this is bulletproof. My understanding is that if the Mac version is set to 10.6, but the iOS version is set to 3.2, it will still use the new constants even if it's compiling for iOS 3.2, which seems incorrect.
Thanks in advance for any help!

You've made a mistake in your observations. :)
TARGET_OS_MAC will be 1 when building a Mac or iPhone application. You're right, it's quite useless for this sort of thing.
However, TARGET_OS_IPHONE is 0 when building a Mac application. I use TARGET_OS_IPHONE in my headers all the time for this purpose.
Like this:
#if TARGET_OS_IPHONE
// iOS code
#else
// OSX code
#endif
Here's a great chart on this:
http://sealiesoftware.com/blog/archive/2010/8/16/TargetConditionalsh.html

The macros to use are defined in the SDK header file TargetConditionals.h. Taken from the 10.11 SDK:
TARGET_OS_WIN32 - Generated code will run under 32-bit Windows
TARGET_OS_UNIX - Generated code will run under some Unix (not OSX)
TARGET_OS_MAC - Generated code will run under Mac OS X variant
TARGET_OS_IPHONE - Generated code for firmware, devices, or simulator
TARGET_OS_IOS - Generated code will run under iOS
TARGET_OS_TV - Generated code will run under Apple TV OS
TARGET_OS_WATCH - Generated code will run under Apple Watch OS
TARGET_OS_SIMULATOR - Generated code will run under a simulator
TARGET_OS_EMBEDDED - Generated code for firmware
Since everything is a “Mac OS X variant” here, TARGET_OS_MAC is not useful in this case. To compile specifically for macOS, for example:
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR && !TARGET_OS_EMBEDDED
// macOS-only code
#endif
Update: Newer headers (Xcode 8+?) now have TARGET_OS_OSX defined specifically for macOS. (h/t #OldHorse), so this should work:
#if TARGET_OS_OSX
// macOS-only code
#endif

"The correct thing to do is just use the newer constants, because if you look at the header you will see they are declared equivalent to the old ones in the enum, which means the new constants will work even on the old releases (both constants compile to the same thing, and since enums are compiled into the app they can't change without breaking binary compatibility). The only reason not to do that is if you need to continue building agains the older SDKs (that is a different thing than supporting older releases, which you can do while compiling against the newer SDKs).
If you actually wanted to use different flags based on the OS version (because the new version actually added new functionality, as opposed to just renaming a constant) then there are two sensible things you can do, neither of which your above macro accomplishes:
To always use the old flags unless the min version allowed is greater than version they were introduced in (something like this):
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
NSDataReadingOptions options = NSDataReadingMapped;
#else
NSDataReadingOptions options = NSMappedRead;
#end
Conditionally use only the new values in builds that can on only the new versions, and compile in code to determine the flags at runtime for builds that support both versions:
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
NSDataReadingOptions options = NSDataReadingMapped;
#else
NSDataReadingOptions options;
if ([[UIDevice currentDevice] systemVersion] compare:#"4.0"] != NSOrderedAscending) {
options = NSDataReadingMapped;
} else {
options = NSMappedRead;
}
#end
Note that if you actually were doing this comparison a lot you would want to stash the result of the [[UIDevice currentDevice] systemVersion] compare:#"4.0"] somewhere. You also generally want to explicitly test for features using things like weak linking instead of doing version compares, but that is not an option for enums.

The set of macros to use includes now TARGET_OS_OSX:
TARGET_OS_WIN32 - Generated code will run under 32-bit Windows
TARGET_OS_UNIX - Generated code will run under some Unix (not OSX)
TARGET_OS_MAC - Generated code will run under Mac OS X variant
TARGET_OS_OSX - Generated code will run under OS X devices
TARGET_OS_IPHONE - Generated code for firmware, devices, or simulator
TARGET_OS_IOS - Generated code will run under iOS
TARGET_OS_TV - Generated code will run under Apple TV OS
TARGET_OS_WATCH - Generated code will run under Apple Watch OS
TARGET_OS_BRIDGE - Generated code will run under Bridge devices
TARGET_OS_SIMULATOR - Generated code will run under a simulator
TARGET_OS_EMBEDDED - Generated code for firmware
Seems to work ok for conditional compilation of macOS code.

Related

Xcode 9.1 upgrade to Swift 4 breaks #ifdef

I've got a project written in Swift 3 that has a number of #if ... #elses scattered throughout; they just check for a certain variable (defined with the -D compiler flag) which is set by my xcode project to know if the project is being built in xcode or with the package manager and does some imports accordingly. For example,
#if XCODE_BUILD
// do some imports that work when built with xcode
#else
// do some imports that won't work when built with xcode
#endif
The code builds just fine via either method.
But, when I select the option to upgrade to Swift 4 (either of the options offered -- "Minimize inference" or "Match Swift 3 behavior"), the code fails to compile so the migration fails. It appears that the #ifs are not being respected or the XCODE_BUILD variable isn't being defined, since the failures occur in the imports that shouldn't happen when being built from Xcode.
Does Swift 4 do something different with #ifs? Does Xcode somehow not define the compiler flags while doing the migration?
You can use #if, #else and #endif, resulting in:
#if XCODE_BUILD
// do some imports that work when built with xcode
#else
// do some imports that won't work when built with xcode
#endif
Apple docs here.
Another answer with some additional details can be found here: https://stackoverflow.com/a/24152730/118091
Previously, I was using the 'Other Swift Flags' build setting in Xcode to pass '-DXCODE_BUILD'. Apparently that setting doesn't work for Swift 4. The new setting that does work is 'Active Compilation Conditions' (it should be set to include XCODE_BUILD, no need for the -D flag).

What does this mean? #if !(arch(x86_64) || arch(arm64))

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 :)

Testing for weak-linked symbol in iOS does not work as expected

I am running into a weird issue when trying to test for the existence of a symbol that is introduced in a newer version of the OS. I follow the Apple guidelines on using weak-linked symbols, i.e.
Check the availability of an external (extern) constant or a
notification name by explicitly comparing its address—and not the
symbol’s bare name—to NULL or nil.
To reproduce the issue, I am using the latest iOS 6 SDK on the latest Xcode 4.5.2, using the default compiler (Apple LLVM compiler 4.1). I weak-linked the Social framework (which is only available on iOS 6+). And I run this code on iOS 5.1 (the deployment target is lower than 6):
NSLog(#"%p", &SLServiceTypeFacebook);
if (&SLServiceTypeFacebook)
NSLog(#"Yes1");
if (&SLServiceTypeFacebook != NULL)
NSLog(#"Yes2");
The output is:
0x0
Yes1
Yes2
In other words, we can verify at runtime that the expression &SLServiceTypeFacebook evaluates to the value 0. Yet, if statements that test on this expression treat it as if it is true.
Update:
From this question, I found that this workaround works with no optimization, but not with optimization:
typeof(&SLServiceTypeFacebook) foo = &SLServiceTypeFacebook;
if (foo)
NSLog(#"Yes3"); // does not get executed on -O0, but does on any optimization
Update:
It appears that this problem does not exist with UIKit symbols. Running the following on iOS 4.3:
NSLog(#"%p", &UIKeyboardDidChangeFrameNotification);
if (&SLServiceTypeFacebook)
NSLog(#"Yes1");
if (&SLServiceTypeFacebook != NULL)
NSLog(#"Yes2");
The output is:
0x0
I hypothesize that the difference is that the UIKit symbol has a NS_AVAILABLE_IOS() macro next to it, so somehow the compiler handles it correctly. In the case of the Social framework symbol, it doesn't have a NS_AVAILABLE_IOS() macro since the entire Social framework itself is only available since iOS 6 (i.e. the symbol is available since the version of the framework, so I guess the don't need this macro?); but then the compiler does not handle the symbol correctly.
Are you sure you don't want to check that the SLRequest class exists instead of checking for this constant?
In any case, the issue is that the compiler is optimizing the test away (it interprets this as testing a constant expression which is true at compile time). You can circumvent this by reading this address into a local volatile variable. Or you could dynamically search for the symbol at runtime.
But I would consider just checking for the SLRequest class instead.
Here are at least these three options:
#include <dlfcn.h>
NSString* const * volatile check = &SLServiceTypeFacebook;
if (check != NULL)
NSLog(#"SLServiceTypeFacebook is defined");
// Another approach would be to call dlsym() at runtime
// to search for this symbol:
if (dlsym(RTLD_DEFAULT, "SLServiceTypeFacebook"))
NSLog(#"SLServiceTypeFacebook found via dlsym");
// But if you really just wanted to know is if SLRequest
// is available, you should really just do this:
if ([SLRequest class])
NSLog(#"SLRequest class is available");
Any of these should work as you were expecting in iOS5.1 versus iOS6.
just check with NSClassFromString if you can get the classes.. objC is all classes anyway :D

How to detect iPhone OS version in the app?

I would like to detect iPhone OS version in the app, can you post sample code as well. I tried using macro that didn't help.
You need to use the macros if you want conditional compilation:
#if __IPHONE_8_0
// Works on >= version 8.0
#else
// Works on < version 8.0
#endif
Or alternatively, to check at runtime, use:
float ver = [[[UIDevice currentDevice] systemVersion] floatValue];
if (ver >= 8.0) {
// Only executes on version 8 or above.
}
In addition to the compile-time checks given in other answers, you can use the Swift's #available attribute to add new functionality starting at a specific OS version.
if (#available(iOS 13.4, *)) {
// Only executes above version 13.4.
}
Here's a decent summary of the evolution of OS version checking and usage.

How to detect iPhone OS version using macros

I would like to detect iPhone OS version in my app. I have tried using detection code but was advised to use macros. Does someone experience, can you post sample code, and what library to use.
I am not certain what macros you are being advised to use. I always thought that the standard way to find the version number so that it will be compatiable with future and previous versions was to use
NSString* versionNumber = [[UIDevice currentDevice] systemVersion]
which gives it as a NSString such as #"2.0" or #"2.2.1"
There are the Version constants that describe the version of Foundation classes being used, with NSFoundationVersionNumber but I am uncertain of how reliable this will be in older and future code.
Look in Availability.h, specifically the statements:
#define __IPHONE_2_0 20000
#define __IPHONE_2_1 20100
#define __IPHONE_2_2 20200
#define __IPHONE_3_0 30000
#define __IPHONE_NA 99999
And don't forget to read the giant header comment.
The preprocessor macros are definitely the safest way to section out your code by os version.