How do you make an Objective-C API feel native in Swift?
Predict first
What does this print?
print(Calc().total(items: 3))
- 3
- 6
- Compile error
Predict first
What does this print?
print(Level.high.rawValue)
- 1
- 0
- Compile error
Mental model. Subtitles for a film: the story is unchanged, but the audience does not have to read the original language.
Swift imports Objective-C by mechanical rules, which can produce awkward names. You can shape the result from the header. NS_SWIFT_NAME(total(items:)) renames a method, property or type for Swift only. NS_REFINED_FOR_SWIFT imports it with a leading __ so you can wrap it in a nicer Swift extension. NS_SWIFT_UNAVAILABLE("use X") hides a method that has a better Swift equivalent. NS_ENUM and NS_OPTIONS import as real Swift enums and option sets, with the common prefix removed from the cases (LevelHigh becomes .high), and NS_CLOSED_ENUM tells Swift the enum will never gain cases so a switch needs no default.
The goal is that a Swift caller never has to know the API came from Objective-C. For third-party headers you cannot edit, API notes files (.apinotes) apply the same renames and nullability without touching the source. Good naming is what makes a mixed codebase pleasant, and it costs almost nothing to add while you are already annotating nullability.
NS_SWIFT_NAME(total(items:)) gives the method its Swift name. The implementation doubles its argument.
NS_ENUM imports as a Swift enum with the Level prefix stripped, so LevelHigh is .high, with raw value 1.
Common traps
- Renaming in Swift with a wrapper but leaving the ugly name public. Mark the original
NS_SWIFT_UNAVAILABLEor refined. - Forgetting
NS_ENUMand using a plainenum. It imports as loose integer constants, with no type safety.
They might follow up with
- How are cases named? The common prefix shared with the type name is dropped and the rest is lower-camel-cased.
- What are API notes for? Applying Swift names and annotations to a header you cannot modify.