!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> » Cross SDK Code Hygiene in Xcode -- Mobile Perspectives

Cross SDK Code Hygiene in Xcode



By deans ~ June 9th, 2009. Filed under: Resources.

I am a big believer in clean compiles.  Unless it’s absolutely impossible, which is rare, I want my code to compile with no warnings.  This is partly just discipline, but it’s also because I’ve run into cases where I missed real problems because I didn’t notice the issue amongst the compiler warnings that I was ignoring.

With 3.0, several methods that were used in 2.x.y are now deprecated.  Use of these methods will generate warnings in 3.0.  Unfortunately, the correct code for 3.0 won’t work in 2.2.1, for example:

Perfectly good 2.2.1 code:

    [startButton setFont:playFont];

Clean compiling 3.0 code:

    [[startButton titleLabel] setFont:playFont];

So we’re faced with a problem.  How do we write code that compiles cleanly in 3.0, while maintaining our ability to do clean 2.2.1 (I’m tired of the 2.x.y notation – just assume that I’m talking generically about version 2 of the SDK) builds?

When we first encountered this, I found several suggestions, most of which were wrong to one degree or another.  As nearly as I can tell, the method that I’m suggesting is more generally workable.

Restating the problem, we need to write our code so that it compiles cleanly, and works, with both a strictly 2.2.1 version of the SDK and with a 3.0 version.  We get extra bonus points if the code won’t need to be revised when the successor to 3.0 comes out.

Because of the requirement to continue to work with strict 2.2.1, we can’t rely on the preprocessor constant for 3.0 being defined.  There’s also an interesting issue between the simulator and the device that I’ll cover later.

Anyway, here’s what we came up with:

#ifndef __IPHONE_3_0
#define __IPHONE_3_0 30000
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0
    [[startButton titleLabel] setFont:playFont];
#else
    [startButton setFont:playFont];
#endif

When I’m building against the 3.0 SDK, __IPHONE_3_0 is defined by Xcode, so my directives don’t interfere.  If all you wanted was the answer, you can stop here.  If you’d like more background on this issue, please continue reading.

First, a bit about where the pertinent preprocessor #defines are specified.  The file is Availability.h

For the simulator, the full pathname to Availability.h is:

    /Developer/Platforms/iphoneSimulator.platform/Developer/SDKs/ 
      …   iPhoneSimulator2.2.1.sdk/usr/include/Availability.h

For the device the path is:

    /Developer/Platforms/iphoneOS.platform/Developer/SDKs/ 
      …   iPhoneOS2.2.1.sdk/usr/include/Availability.h

In the version of Availability.h that goes along with 2.2.1, the following are defined:

#define __IPHONE_2_0 20000
#define __IPHONE_2_1 20100
#define __IPHONE_2_2 20200

Note that there is no __IPHONE_2_2_1

Now, here’s the tricky bit.  With Xcode, the mechanisms for communicating the version of the SDK that you’re using differ between builds for the simulator and builds for the device.  When compiling for the simulator, Xcode’s default configuration for a 2.2.1 build sets a flag as follows:

    -D__IPHONE_OS_VERSION_MIN_REQUIRED=20000

If you’re paying attention, you’ll notice that, when building for the simulator, targeting the 2.2.1 SDK, Xcode is setting the flag to 20000.

When building for the device, on the other hand, Xcode uses a completely different technique.  Instead of defining a version value for the preprocessor, the preprocessor substitutes:

    __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__

in place of:

    __IPHONE_OS_VERSION_MIN_REQUIRED.

Xcode (according to the comments in AvailabilityInternal.h) then has the compiler set:

    __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__

by using -miphoneos-version-min.  The actual compile directive when building for the 2.2.1 SDK is:

    -miphoneos-version-min=2.2.1

The implication of this is that, when you build for the simulator, the following code:

    NSLog(@"MIN VERS REQD: %d -- Current Version: %d", 
      …   __IPHONE_OS_VERSION_MIN_REQUIRED,__IPHONE_2_2);

Causes this string to be output to the console:

    MIN VERS REQD: 20000 -- Current Version: 20200

The exact same code, running on the actual device, produces:

    MIN VERS REQD: 20201 -- Current Version: 20200

The reason I shared this in painful detail is to explain why some of the solutions that people are suggesting for this issue might not work in the general case.

Assuming that we’re building for the simulator as many of us do during development, the default Xcode configuration won’t let us find out whether we’re using any SDK later than 2.0.  For example, if we have code that goes something like:

// DO NOT DO THIS
#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_2_2
    [[startButton titleLabel] setFont:playFont];
#else
    [startButton setFont:playFont];
#endif

This code will work on the simulator because __IPHONE_OS_VERSION_MIN_REQUIRED will be 20000, which is not greater than 20200.  It will not work on the device, however, because 20201 is greater than 20200.  Remember that there is no official definition of _IPHONE_2_2_1.  Sigh…

Another approach that I considered was:

// THIS MIGHT WORK, BUT I DON’T RECOMMEND IT
#ifndef __IPHONE_3_0
    NSLog(@"THE 3.0 SDK IS NOT DEFINED");
#else
    NSLOG(@"THE 3.0 SDK IS DEFINED");
#endif

The problem here is that I’m not testing the value, I’m simply seeing if it’s defined.  If the variable ever gets defined in my code (perhaps due to a bit like what I’m proposing) or if the Xcode ever picks up a version of Availability.h that defines __IPHONE_3_0 when you’re building for 2.2.1 (I know that this shouldn’t happen, but better safe than sorry) the preproccesor will eliminate the wrong lines of code.  Remember that there are multiple versions of Availability.h kicking around on your system.  Check out the one at /usr/include/Availability.h sometime.

 

——–
Technorati Tags:  , , , ,,

Comments are closed.