I've been thinking about the right way to use NS_ENUM vs NS_CLOSED_ENUM for Objective-C enums in a large iOS codebase. Our codebase is a mixture of ObjC and Swift modules, with enums often defined in ObjC and consumed in Swift. This is internal app code, not a public library/framework. I'd love to hear how others approach this.
My initial reading
After reading SE-0192, I came away thinking NS_CLOSED_ENUM is safe to use broadly in internal code, since the frozen/non-frozen distinction was introduced primarily to address binary compatibility and library evolution. The proposal explicitly states:
"the core team has made it clear that such a distinction is only worth it for libraries that have binary compatibility concerns"
For internal code that always recompiles together, binary compatibility isn't a concern — when a case is added, all consumers are recompiled.
Apple's documentation seems to support this...
The Foundation Release Notes also frames it around binary compatibility:
"Once an enumeration is marked as closed, it's a binary- and source-incompatible change to add a new value."
...but another Apple doc takes a different angle
Grouping Related Objective-C Constants takes a more conceptual approach:
Use the NS_CLOSED_ENUM macro for a simple group of constants that you can never add new cases to. Closed enumerations are useful for representing a finite set of states...
Don't use the NS_CLOSED_ENUM macro if:
- You've ever added cases to an enumeration after its initial declaration
- You can think of additional cases you might add later
- The enumeration has any private cases"
This framing is less about binary compatibility and more about semantic accuracy — if an enum isn't a "mathematically complete" finite set, don't call it closed.
My question
For those working on large iOS codebases with a mixture of ObjC and Swift modules (where ObjC-defined enums are frequently consumed by Swift code):
- Do you use
NS_CLOSED_ENUMbroadly for internal enums (prioritizing Swift ergonomics and avoiding@unknown defaultboilerplate)? - Or do you stick with
NS_ENUMfor anything that might grow (prioritizing conceptual clarity)? - Have you run into any practical issues with either approach at scale?
I'm genuinely trying to understand the tradeoffs here, not advocate for either side. Thanks!