blog.lukhnos.org

Quick Notes on Writing Code that Works on All Current Apple Operating Systems

All combined, there are 10 Apple operating systems you might need to support:

In between, in the OS X 10.5 and 10.6 zone, you might want to add gc and non-gc support as options.

There are six aspects to consider:

  1. Your target platforms and SDKs
  2. Endianness
  3. Width and type of the primitives
  4. Logging of the primitives
  5. Using only the common denominators in the system library
  6. Conditional compilation

Here are the details.

  1. Your target platforms. If you don't need to target OS X 10.4 Tiger, then everything is really simpler. iPhone SDK shares largely the same foundation of OS X 10.5 SDK with Objective-C 2.0 support (except gc) and all the new types and methods in Foundation. If you still need to target Tiger, your options are (1) using entirely the 10.4 SDK, or (2) using 10.5 SDK but targeting 10.4. Although (1) is the easier way, it's really the last option I recommend you to take. The second path requires more careful planning (more on that later) but it makes your code more forward compatible especially if your code will eventually run on 64-bit platforms.

  2. Endianness. Turns out this is the easiest to solve especially if you don't naïvely do primitive data serialization, which you shouldn't do anyway because of the issues of modern C (e.g. that compilers do so many things for you, plus that modern computer architecture is adamant on alignment). Stick to the fact that other than PowerPC everything else (Intel, ARM on iPhone) is little endian. The old-style Apple's 32-bit constant (things like 'lpcm' which is the value of the constant kAudioFormatLinearPCM in Audio Toolbox) is read 'cmlp' if you dump the 32-bit word on Intel.

  3. Width and type of the primitives. I have witnessed the 16-bit to 32-bit transition when I was in middle school, and anyone who has done their bit in Win16 would told you the folly of Hungarian notation and the historical debacles on types such as WORD, DWORD, and so on. This issue is now alive once again as many of us are now targeting 64-bit. Gladly the problem is well understood and people now know better. Use fixed-width primitive typedefs like uint8_t or uint32_t is the common practice nowadays. For standard C code, use things like size_t and SIZE_MAX for POSIX-land or platform-agnostic code, and they'll do fine on Windows and Linux too. As for Cocoa, since 10.5 SDK there are new typedefs which Apple now use throughout their framework code: NSInteger, NSUInteger, CGFloat, and so on. NSInteger and NSUInteger is the widest (unsigned) integer on the platform you're targeting. (int and long aren't reliable types by name because of history). This is the most compelling reason you should use OS X 10.5 SDK even if you're targeting Tiger. For framework designers, however, chances are your code might be required to be built under both 10.4 and 10.5 SDK. My solution is to use typedefs in the implementation files.

  4. Logging of the primitives. Use %ld for NSInteger and %lu for NSUInteger as recommended by Apple.

  5. Using only the common denominators in the system library. This is unfortunately the trickier part. Again the most important things are:

    • Some methods don't exist on iPhone. -[NSObject className] for one. Use NSStringFromClass() instead. Take extra care of NSFileManager methods. Many of them don't exist there either!
    • Hateful -[NSNumber integerValue]. Unfortunately this is the biggest problem if you are using 10.5 SDK and still targeting 10.4. You call respondsToSelector:@selector(integerValue) and compiler gives you warning—but a category can shuts the compiler warning off. Not for performance-intensive code. Should probably consider conditional compilation.
    • Zapped functions. If you are calling a C function that only exists in OS X 10.5 SDK when running on 10.4, dyld is smart enough to point those functions to NULL. So do if (x) before you call x() if it's a 10.5-only function.
    • Stop casting CF objects to NS objects to autorelease them. This stops working in gc mode, because autorelease is an NOP in gc. Use NSMakeCollectable, which is actually a CFMakeCollectable wrapper but is nil-safe. And yea, Apple is benevolent enough to have supplied that function in 10.4 SDK (at around OS X 10.4.8 or 10.4.9, I can't exactly recall), so you don't need to check if that's a zapped function.
  6. Conditional compilation. This is the biggest bag of hurts. I only use these variants.

    To determine if if it's on OS X 10.4 SDK, targeting 10.4:

    #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4    
    #endif
    

    To determine if we're on OS X 10.5 SDK/iPhone SDK, able to use Objective-C 2.0 and all:

    #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
    #endif
    

    To determine if we're on OS X 10.5 SDK (but probably targeting 10.4 too):

    #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
    #endif
    

    If on iPhone:

    #if TARGET_OS_IPHONE
    #endif
    

    If you have AppKit (i.e. on Mac but not on iPhone), e.g. for NSModalPanelRunLoopMode:

    #if TARGET_OS_MAC && !TARGET_OS_IPHONE
    #endif
    

    If on iPhone Simulator:

    #if TARGET_IPHONE_SIMULATOR
    #endif
    

ObjectiveFlickr have actually done all the things above, and that's how it can support a great number of current Apple platforms (I can't claim all, since I haven't done a complete coverage test).