NSOnState is deprecated

July 1, 2018

By Jeff Johnson

The AppKit Release Notes for macOS 10.14 have finally been released. In the overview, we are told to pay special attention to the section "Swift and Objective-C API Enhancements":

AppKit's Swift interface is improved in macOS 10.14 in pursuit of greater clarity, consistency, concision, and a native Swift feel. In many cases, these enhancements go in hand with refinements to the corresponding Objective-C APIs. The changes include formalizing informal protocols; moving enumerations to a common-prefix identifier convention…

I'm going to discuss one change in the Objective-C API, because it is both illustrative and egregious. In the 10.14 SDK, the Objective-C constants NSOnState, NSOffState, and NSMixedState have been deprecated:

typedef NSControlStateValue NSCellStateValue NS_DEPRECATED_WITH_REPLACEMENT_MAC("NSControlStateValue", 10_0, 10_14);
static const NSControlStateValue NSMixedState NS_DEPRECATED_WITH_REPLACEMENT_MAC("NSControlStateValueMixed", 10_0, 10_14) = NSControlStateValueMixed;
static const NSControlStateValue NSOffState NS_DEPRECATED_WITH_REPLACEMENT_MAC("NSControlStateValueOff", 10_0, 10_14) = NSControlStateValueOff;
static const NSControlStateValue NSOnState NS_DEPRECATED_WITH_REPLACEMENT_MAC("NSControlStateValueOn", 10_0, 10_14) = NSControlStateValueOn;

Notice that these Objective-C constants were introduced in the 10.0 SDK. They've been around for the entire history of Mac OS X. If you look at the Swift API, it's pretty obvious where the new names came from:

extension NSControl.StateValue {

    public static let mixed: NSControl.StateValue

    public static let off: NSControl.StateValue

    public static let on: NSControl.StateValue

My question is, why did the old constants have to be deprecated? Swift already exports a significantly different API than Objective-C for all of Cocoa, so there's no necessity that the two API match exactly. Why change and "Swiftify" the API that Objective-C programmers are familiar with and have already been using for the entire 21st century?

Some people claim that these API changes are a sign that Apple still cares about Objective-C. The changes are supposedly a "modernization" of the API. However, I don't believe these claims for a second. Swift programmers enjoy and celebrate the brevity of their API. The AppKit Release Notes even mention "concision" as a goal. Yet the new Objective-C symbol names are longer, not shorter. Here's how the API was used before:

button.state = NSOnState;

And after:

button.state = NSControlStateValueOn;

Now compare with Swift:

button.state = .on

Swift programmers would surely complain vehemently if they had to type this every time:

button.state = NSControl.StateValue.on

As far as I can tell as an Objective-C programmer, the API change was just to follow the pattern of Swift, and doesn't help Objective-C in any way.

The cost-benefit ratio of deprecating these long-standing Objective-C API seems absurdly one-sided. How many problems are introduced by deprecating the old constants, and how many problems are solved by creating the new constants? I'm not aware of anyone who was confused by NSOnState. Was it a source of bugs? In my opinion, the new constants are more confusing, because "StateValue" is a bizarre, unhelpful construction. Meanwhile, deprecation causes an enormous amount of inconvenience. Countless lines of existing code written over the years will start to throw deprecation warnings, forcing code maintainers to either ignore the warnings or change all the code.

There's a misguided notion that while changing the API may make it harder for longtime users, the changes will make it easier for new people to learn the API. In practice, though, nothing could be further from the truth. The old symbols do not simply disappear from the face of the Earth. When an API has been in use for an extended period of time, the previous symbols will continue to exist in documentation, in older code, and on the web for many years to come, despite the deprecation. Ironically, the new API that was supposed to reduce confusion results in even more confusion. Instead of only having to learn one symbol, new programmers have to learn two symbols for the same thing. This situation, where there are multiple API with the exact same functionality, is one of the most confusing predicaments for newcomers.

The customary purpose of deprecation is to prepare programmers for the functionality to be removed in the future. It's an abuse of the practice for API maintainers to deprecate and revise rather than deprecate and remove. The perfect is the enemy of the good. There is no danger of the functionality behind NSOnState getting removed, and in fact there's a very good reason that NSOnState is defined the same as NSControlStateValueOn. For binary compatibility, NSOnState can never stop working. When you compile NSOnState, it simply becomes the value 1 in the app's executable code:

callq	*0x26e1c(%rip) ## Objc message: -[%rdi state]
xorl	%ecx, %ecx
cmpq	$0x1, %rax

That precise value has to be handled correctly at runtime in future versions of macOS. Thus, NSOnState can never change, and I see no good reason why the symbol name should change.


To clarify a technical matter: the new 10.14 deprecations will not produce build warnings if you simply compile against the 10.14 SDK. You also have to set the deployment target to 10.14 in order to see the warnings. This practically guarantees that the old symbols and the new symbols will coexist in the wild for many years to come, as I suggested above.

It's also a bit of a cop-out by the API maintainers, because the delayed change effectively shields them from a lot of immediate feedback and fallout. The codebases most affected by the deprecations will likely not feel the pain until some future year. That's why I'm trying to draw attention the the issue now.

Addendum 2

It has been suggested to me that the new constants are an improvement because they make Xcode autocomplete a little easier. You can type the prefix of the enumeration, and then Xcode's autocomplete list will contain the enumeration constants. However, this seems to me to be "solving" the wrong problem. Why doesn't Xcode autocomplete Just Work™? Xcode has all of the type information, so why can't it show the enumeration constants in the autocomplete list already, without having to type a prefix? Moreover, this isn't even helpful if you don't know or can't guess the prefix, which may not be obvious. It's a fundamental failure that Xcode autocomplete is so primitive and ignorant, an embarrassment for a 15 year old developer tool. The wrong tradeoff is to hack the frameworks and introduce piles of deprecations into every existing codebase in order to try to cover up for Xcode's shortcomings. Instead, Apple needs to fix Xcode autocomplete. How has this not been a top priority? They rewrote the source editor from scratch, but autocomplete is still terrible?