Deployment Targets

Deployment Targets are a powerful concept on the Cocoa platform that allow you to build applications that take advantage of new features in the latest platform SDKs, but can still run on older versions of the operating system. For example, you might want to build your application against the latest iOS SDK to take advantage of all the new capabilities, but set a Deployment Target of 6.0 or 7.0 to let users with older devices still use your application – albeit with possibly reduced functionality.

Which SDK(s) to build for is controlled by two settings in Project Properties, the Target SDK and the Deployment Target.

The Target SDK is the main setting; it tells compiler and linker what version of the SDK you want to build against. This setting defines which of the provided SDK folders with .fx Files the compiler will use, and which classes and other code elements will be available to the compiler. To use features from a new SDK (without runtime hacks or other nasty workarounds), you will need to build against a version of the SDK that contains the feature. In many cases, as iOS and OS X evolve, the choice of Target SDK setting will also drive the operating system to give different behavior to your application by letting it know that it was built to support the new operating system.

For example, to get the new look of standard iOS controls in iOS 7, your application needs to be built with a Target SDK setting of iOS 7.0 (or later, of course). In order to support a 4" screen in iPhone applications, your application needs to be built against iOS 6.0 (or later), and so on. Applications built against an older Target SDK will often run in "compatibility mode" and not gain access to certain new features.

Of course Elements lets you select an explicit Target SDK version in Project Properties (such as "iOS 6.1" or "OS X 10.8") or you can use the more generic setting of "iOS" or "OS X", which causes the compiler and tool-chain to pick the latest version of the SDK that is both supported locally by your .fx Files and the version of Xcode you are using. This is a convenient way to avoid hardcoding an SDK version in your project and make upgrading easier – but it is important to realize that this is just a short-hand: the compiler will still pick a definite Target SDK setting at compile time, and your application will behave as if you had explicitly specified the exact version.

In other words, if you're building your application with the Target SDK set to "iOS", and your Xcode version is 4.6.3, for example, your application will built just as if you had selected "iOS 6.1" explicitly. When you later update to Xcode 6, new builds will compile for "iOS 8.0" automatically, and so on.

By default, applications will only run on versions of the operating system that are the same or newer as the Target SDK. So if you build an application with the "OS X 10.7" Target SDK, it will refuse to run on OS X 10.6, for example. This is where the Deployment Target setting comes in.

The Deployment Target setting does not directly affect what the compiler sees. All it does is mark your application as being OK to run on versions of the operating system that may be older than the Target SDK. For example, if you build your iOS application against "iOS 7.0", but set the Deployment Target to "5.0", the operating system will allow your application to run on any device with iOS 5.0 or later.

Limitations & Solutions

Of course there is no such thing as a free lunch, and there are several limitations to keep in mind when building with Deployment Targets. Most importantly, your use of classes or members that are introduced in newer versions of the SDK is affected.

As mentioned above, it is the Target SDK setting that drives what the compiler sees. If your Target SDK is set to "iOS 8.0", the compiler sees and will let you freely use all the functionality that is provided by iOS 8 – even if that functionality might not be available on iOS 7 or lower. If you use any of the missing functionality, your application will most likely crash at runtime when it tries to instantiate a class that does not exist, or call a method that's not implemented in the older operating system.

But Elements adds a number of compiler and tool-chain features that make it really easy to support different Deployment Targets.

Weak Linking

Elements knows from meta-data in the .fx files which code elements are available in what versions of the SDK. If you are building with a Deployment Target setting that is lower than the Target SDK, Elements will automatically use so-called "weak linking" to refer to any external types or constants that may not exist on older operating systems at runtime.

What this means is that instead of crashing on application start due to missing symbols (as you would, for example, expect on Windows, if you imported functions from a .dll that did not exist on an older OS), your application will launch fine, and those elements will simply be nil.

This means, of course, that you need to be careful in your code to not use classes that are not available – after all, there's no magic in the world that the compiler could do to let you actually use a class that simply does not exist in the version of the OS your app is running on. But this is easily achieved by simply checking for nil.

The snippet below shows the common use case of adding a "Pull to Refresh" control to a UITableView on iOS 6 without breaking the application for iOS 5 and below:

if assigned(typeOf(UIRefreshControl)) then begin
  refreshControl := new UIRefreshControl();
  refreshControl.addTarget(self)
                 action(selector(refresh:))
                 forControlEvents(UIControlEvents.UIControlEventValueChanged);
end;
if (typeOf(UIRefreshControl) != null)
{
  refreshControl = new UIRefreshControl();
  refreshControl.addTarget(this)
                 action(__selector(refresh:))
                 forControlEvents(UIControlEvents.UIControlEventValueChanged);
}
if UIRefreshControl.Type != nil {
  refreshControl = UIRefreshControl()
  refreshControl.addTarget(this,
                 action: "refresh:",
                 forControlEvents: .UIControlEventValueChanged)
}

Readers familiar with the concept of Deployment Targets from Objective-C will notice that Elements even goes a step further with weak linking than Xcode and Objective-C. In Objective-C, directly referring to a class by name would cause the application to fail to load, requiring hacks such as

[[NSClassFromString(@"UIRefreshControl" alloc] init];

to dynamically find the class at runtime. With Elements that is not necessary.

Another common scenario you will find and use is to check for the availability of new methods on old classes. So the above assigned() check could have been rewritten as

if respondsToSelector(selector(refreshControl)) then begin
  ...
if (respondsToSelector(__selector(refreshControl))
{
    ...
if (respondsToSelector("refreshControl") { ...

since that method/property would be missing on iOS 5.

The above example checks whether typeOf(UIRefreshControl) is available to determine if the class can be used, but essentially any way of avoiding the code path on older OS versions is valid as well, of course. Maybe your application checks the OS version on start-up and loads totally different views for older OS versions than newer ones, making detailed checks unnecessary.

Deployment Target Hints

Elements has a project setting called "Deployment Target Hints". When enabled, the compiler will emit hints for any code elements you use that are not available on your lowest Deployment Target. For the above code snippet, it would notify you about allocating the UIRefreshControl class, and about calling the UITableView.refreshControl property.

New in Version 8.2, you can use the available() System Function to protect code against running on OS versions it is not supported on and omit deployment target hints in the process.

The idea is that you will turn this setting on temporarily and review any hints that get reported to make sure that the corresponding code is protected sufficiently against running on older versions of the OS. Once satisfied, you would turn the hints back off, or surround the individual areas with the proper if available() checks or {$HIDE NH0}...{$SHOW NH0} Compiler Directives.

In essence, Deployment Target Hints are a first line defense to find places where you need to write version-specific code before your extensive testing and unit testing would catch the crash that happens for trying to use a non-existing class.

Dedicated rtl.fx files

In addition to some types, members and constants not being available on older Deployment Targets, there can also sometimes be fundamental differences in core system RTL types. For example, GCD queues became ARC-compatible in iOS 6, but are not in iOS 5, something that is important for how the compiler generates the executable, even if it won't (usually) affect the code you write yourself.

To allow for this, each SDK import comes with multiple versions of rtl.fx that provide compatibility information about older Deployment Targets. You do not need to handle this manually (or even be aware of this, really), but the compiler will automatically pick rtl-5.1.fx instead of the regular rtl.fx if you are building for an iOS 5 Deployment Target.

Weak References on iOS 4 and OS X 10.6

Automatic Reference Counting (ARC) was introduced with iOS 5 and OS X 10.7, but it is backwards compatible with older versions of iOS and OS X, allowing you to build applications for Deployment Targets of iOS 3 or 4, or OS X 10.5 or 10.6. But there is one exception: weak references (see Storage Modifiers) are not supported on those OS versions.

Elements will emit errors if you are using the weak keyword and your Deployment Target is set to iOS 4 or lower or OS X 10.6 or lower. The only way to build apps for these Deployment Targets is to avoid weak references, possibly changing them to the less convenient unretained type, alongside some extra checks or logic.

Why Bother?

This is all well and good, you say, but surely it must become tedious to be on the lookout for code that may fail on older OS versions? It can be, yes. But supporting different Deployment Targets is an important fact of life on the Cocoa platform – which is why we put so much effort into making the process easier in Elements, with the better weak linking support and the hints, both of which Xcode and Objective-C users need to cope without.

But there is really no simple way around doing this job and proofing your code for older OS versions, if you want to support them. You really only have three options:

  • Continue building against the older SDK (and missing out on any new features and capabilities)
  • Drop support for older OS versions
  • Proper handling of Deployment Target issues

Option one is not really an option in most cases. Old iOS 5 apps will look wrong on an iPhone 5 and 6's larger screens. Most iOS 6 apps will look old and dated on iOS 7 and later. It is generally accepted as best practice (and recommended, if not enforced, by Apple) that you should use the latest shipping Target SDK for your development (and in many cases in the past, Apple has stopped accepting App Store submissions built against older SDKs).

Option two might or might not be feasible, depending on your app's business model. Maybe you can afford targeting the latest OS only, but in many cases you will not want to exclude users on older devices – especially if all the core functionality for your application is supported.

This really just leaves the option of biting the bullet and supporting the full range of OS versions your application needs to support. We hope that we've made this easy and comfortable for you with the improvements in Elements 6.1.