The great thing about building apps for both iOS and Mac is that many pieces of code work just the same on both platforms. There are some scenarios however where you want to add different kinds of Apple SDKs based on which platform you are building for.
A good place to put all headers that often used is the Precompiled Header File (PCH) which gets precompiled and then reused throughout your app. Whenever you have an #import statement in your code the compiler needs to figure out whether this header has already been imported because the same header file can potentially be imported from several locations.
I generally like to put all imports for Apple headers into my PCH file as well as my own app-wide classes like my DTFoundation library which has a growing selection of methods that I frequently use. Having these imports in the PCH means that the preprocessor can prepare them for faster compiling once and then can virtually prepend all these definitions for every source code file.
Today I learned something new, namely how you can use the same PCH for Mac as well as iOS.
The magic term is “Target Conditionals”. This is a header supplied by Apple which defines multiple handy macros for code that is conditional on the target. Hence the name.
The problem here is that if you just try to use these macros they are stubbornly undefined… unless you apply the easy remedy of actually importing it. Consider this PCH file:
#import <TargetConditionals.h> #ifdef __OBJC__ #if TARGET_OS_IPHONE #import <UIKit/UIKit.h> #endif #import <Foundation/Foundation.h> #import "LoadableCategory.h" // DTDownloadCache #import <CoreData/CoreData.h> #endif
Thanks to Jamie Montgomerie who was the first to supply the answer to me on Twitter.
The most interesting defines in TargetConditionals are:
Note however that the iPhone OS is a sub-variant of Mac OS. Because of this TARGET_OS_MAC is also defined when building for iPhone. So if you want to restrict code to only be included on iPhone use TARGET_OS_IPHONE, for Mac-only use #if !TARGET_OS_IPHONE. (Exclamation mark negates it). That is, unless you are writing code for even more platforms than these two. There you would have to include additional target conditionals, like the ones for certain CPUs.
There is another header that is equally as useful as TargetInternals.h. This adds defines that allow you to vary your code based on the deployment target or maximum SDK available. This header is Availability.h, also imported in angle brackets and without a path if needed.
Let’s say you wanted to use weak properties if the deployment target (i.e. minimum iOS version allowed to execute your app) is greater or equal than iOS 5. Zeroing weak references are only available from this version on upwards. Below that level you would use assign properties and tag your ivars with __unsafe_unretained.
#import <Availability.h> #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 #define __WEAK __weak #define WEAK weak #else #define __WEAK __unsafe_unretained #define WEAK assign #endif @property(nonatomic,WEAK) UIView *targetView;
When building for iOS the deployment target is __IPHONE_OS_VERSION_MIN_REQUIRED and the maximum SDK is __IPHONE_OS_VERSION_MAX_ALLOWED. The latter can be used to add code that only works if the SDK you’re building with is at least as high. This way you can for example code with Xcode 4.5 against the iOS 6 SDK, but have this code be omitted on your build server where you might not have upgraded yet.
You can compare these two defines against a number like 50000 or use the corresponding define __IPHONE_5_0. Note that there’s a stumbling block gower when doing so: __IPHONE_6_0 is only defined when building with the 6.0 SDK and would cause an error on earlier compilers. So in this case you’d do either of the following:
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // deployment target is iOS 6 or greater (we wish!) #endif #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1 // building with iOS 6 SDK #endif
You can glance at the defined versions also in Availability.h. Just add the import in Xcode and then CMD+Click on the file name.
Again you can adde the import to the PCH file to also have these defines available, for example you would conditionally import headers there that only became available with the new SDK versions.
One more thing …
I briefly confused myself by adding some #defines to the PCH but getting an error from Xcode that they are unknown. The reason for this was that the app I was building was using a different PCH than the static library that I was adding the defines to. The PCH of a project is only visible to targets that are using it. Doh!