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


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.