SDK vs. Deployment Target

May 4, 2013

(Updated May 5, 2013 to include weak_import)

I used to have a WordPress blog. I no longer update that blog, because it became a PITA to constantly update WordPress. Also, my desire to help people waned after the infiltration of our little Mac community by the horde of iOS developers. No offense. Well, not much offense. Nonetheless, I occasionally have something helpful to say to people. So, I'm just going to write a self-contained article here, with no promise of any future articles. I'm so serious about no promises that I'm not even setting up an RSS feed for articles, and if you know me, that's telling.

Enough chit-chat, on to business. Speaking of business, my company's apps still run on Mac OS X 10.6 Snow Leopard. However, the latest version of Xcode does not contain the Mac OS X 10.6 SDK. As the Church Lady would say, "How convenient!" Therefore, in order to build our apps with the latest Xcode, we have to set the SDK to Mac OS X 10.7 and the Deployment Target to 10.6. The problem with this setup is that if we accidentally use API that only exist on 10.7, then there is no build warning, but the app will crash at launch on 10.6. How convenient!

My friend Paul Kim was lamenting this issue recently, and my friend Daniel Jalkut pointed us toward a possible solution on Stack Overflow. (I call them my friends. They probably hate my guts, but at least we speak frequently.) This solution was designed for iOS, though, and didn't seem to work for Mac OS X. That's where I came in. I decided to hack at it until I found a solution for Mac OS X, and I believe that I did. Put the following code in your prefix header (.pch file):

	#ifdef __OBJC__
		#import <Foundation/NSObjCRuntime.h>
		#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_6
			#undef  AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER
			#ifdef __clang__
				#define AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER __attribute__((weak_import,deprecated("API is newer than Deployment Target.")))
			#else
				#define AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER __attribute__((weak_import,deprecated))
			#endif
		#endif
		#undef  NS_CLASS_AVAILABLE
		#define NS_CLASS_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER
	
		#import <Cocoa/Cocoa.h>
	#endif

We are redefining macros in the system headers. Redefining AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER takes care of methods introduced in 10.7, which are marked in the headers by NS_AVAILABLE() or NS_AVAILABLE_MAC().

	// Available on MacOS and iOS
	#define NS_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER

	// Available on MacOS only
	#define NS_AVAILABLE_MAC(_mac) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER

Redefining NS_CLASS_AVAILABLE takes care of classes introduced in 10.7, which are marked in the headers by NS_CLASS_AVAILABLE() or NS_CLASS_AVAILABLE_MAC(). See <Foundation/NSObjCRuntime.h> for the original definition of those macros, which I won't paste here.

When you use my prefix header above, you will get a deprecation build warning whenever you use 10.7 API with a 10.6 deployment target. Why have a deprecation warning rather than throwing an error? Because this allows you to conditionally use the 10.7 API when running on Mac OS X 10.7. In order to intentionally use 10.7 API, you need to compile out the build warning. For example:

	#pragma clang diagnostic push
	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
		if ( [window respondsToSelector:@selector( setRestorable: )] )
			[window setRestorable:NO];
	#pragma clang diagnostic pop

Obviously, my solution only works with the 10.7 SDK and 10.6 Deployment Target. You'll have to modify it if you use a different SDK or Deployment Target. I haven't been able to think of a general-purpose solution to this problem, that works with all SDKs and Deployment Targets.

That's it for now. Please leave any questions or corrections in the comments section below. Love, Jeff.