Checking for El Capitan

August 2, 2015

Sometimes your app needs to check the Mac OS X version at runtime in order to handle a runtime behavior change in OS X that isn't already handled automatically by the Cocoa API. One standard and Apple-recommended method of checking the runtime version is via NSAppKitVersionNumber. There are constants defined in <AppKit/NSApplication.h> corresponding to each major OS X version update, as well as a number of minor version updates. For example, this is from the OS X 10.10 SDK:

APPKIT_EXTERN const double NSAppKitVersionNumber;

#define NSAppKitVersionNumber10_0 577
#define NSAppKitVersionNumber10_1 620
#define NSAppKitVersionNumber10_2 663
#define NSAppKitVersionNumber10_2_3 663.6
#define NSAppKitVersionNumber10_3 743
#define NSAppKitVersionNumber10_3_2 743.14
#define NSAppKitVersionNumber10_3_3 743.2
#define NSAppKitVersionNumber10_3_5 743.24
#define NSAppKitVersionNumber10_3_7 743.33
#define NSAppKitVersionNumber10_3_9 743.36
#define NSAppKitVersionNumber10_4 824
#define NSAppKitVersionNumber10_4_1 824.1
#define NSAppKitVersionNumber10_4_3 824.23
#define NSAppKitVersionNumber10_4_4 824.33
#define NSAppKitVersionNumber10_4_7 824.41
#define NSAppKitVersionNumber10_5 949
#define NSAppKitVersionNumber10_5_2 949.27
#define NSAppKitVersionNumber10_5_3 949.33
#define NSAppKitVersionNumber10_6 1038
#define NSAppKitVersionNumber10_7 1138
#define NSAppKitVersionNumber10_7_2 1138.23
#define NSAppKitVersionNumber10_7_3 1138.32
#define NSAppKitVersionNumber10_7_4 1138.47
#define NSAppKitVersionNumber10_8 1187
#define NSAppKitVersionNumber10_9 1265

As you can see, major OS X version updates increase the integer part of the constant, while minor OS X version updates increase the fractional part of the constant. This all worked fine for years, even through OS X 10.10. From the AppKit Release Notes for OS X v10.10:

if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_8) {
  /* On a 10.8.x or earlier system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) {
  /* On a 10.9 - 10.9.x system */
} else {
  /* 10.10 or later system */
}

This year, however, something went wrong. In the AppKit Release Notes for OS X v10.11, Apple recommends the same technique as before:

if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) {
  /* On a 10.9.x or earlier system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_10) {
  /* On a 10.10 - 10.10.x system */
} else {
  /* 10.11 or later system */
}

But that doesn't work! If you're running on OS X 10.10.2 or later, the code tells you that you're running on OS X 10.11. What happened? Let's look at <AppKit/NSApplication.h> from the 10.11 SDK:

#define NSAppKitVersionNumber10_10 1343

So far, so good.

#define NSAppKitVersionNumber10_10_2 1344
#define NSAppKitVersionNumber10_10_3 1347

WHUT.

So yeah, instead of increasing the fractional part of the constant for 10.10.2 and 10.10.3, they increased the integer part. Sigh.

The workaround is pretty simple. Unfortunately, NSAppKitVersionNumber10_11 is not yet defined in the headers. Nonetheless, both NSAppKitVersionNumber at runtime and CFBundleVersion in /System/Library/Frameworks/AppKit.framework/Resources/Info.plist on OS X 10.11 give the same value: 1389. Thus, a runtime check for 10.11 would work like this:

if (NSAppKitVersionNumber >= 1389)

It should be noted that there is a new API to check for the OS X version at runtime: -[NSProcessInfo operatingSystemVersion]. However, this method requires OS X 10.10 or higher, so if your app still supports 10.9 or lower, you can't use it yet.

One more thing: the AppKit "engineers" who decided to break from long practice in OS X 10.10.2 should be fired and never allowed to touch a Mac again. It is so ordered.