What is an Objective-C category, and how does it differ from a Swift extension?
Predict first
What does this print? (The category is implemented in Objective-C.)
print(("hi" as NSString).shout())
- hi
- HI!
- Compile error
Predict first
A category in a static library fails at runtime with “unrecognized selector”. Which linker flag usually fixes it?
// A: -ObjC
// B: -dead_strip
// C: -fobjc-arc
- A
- B
- C
Mental model. A category is a sticker you put on a class that every existing object grows immediately. A Swift extension is a signed addendum the compiler reviews first.
A category adds methods to an existing class, even a system class like NSString, without subclassing: @interface NSString (Shout). At load time the runtime splices those methods into the class’s method table, so every instance, including those created by the framework, gains them. From Swift, an imported category method is simply available on the class.
The differences are about safety. A category cannot add stored properties (the class layout is fixed). If a category redefines an existing method, or two categories define the same one, which implementation wins is undefined, with no compiler error. Categories register themselves through the runtime, so in a static library the linker may drop the file that contains only a category, which shows up as unrecognized selector at runtime; the -ObjC linker flag fixes it. A Swift extension is checked at compile time, can have generic constraints, and cannot silently replace a method.
The category method is imported into Swift as an ordinary method on NSString, so the call works on any instance.
-ObjC forces the linker to load every Objective-C class and category from a static library, including files that nothing references by name.
| Objective-C category | Swift extension | |
|---|---|---|
| Adds methods | yes, any class | yes, any type |
| Adds stored properties | no (use associated objects) | no |
| Redefines an existing method | allowed, undefined winner | compile error |
| Generic constraints | no | yes (where clauses) |
| Registered | at runtime load | at compile time |
Common traps
- Overriding a framework method in a category. The result depends on load order, and the original is lost.
- Prefixing nothing. A category method name that collides with a future Apple method silently changes behaviour. Prefix them (
xyz_shout).
They might follow up with
- What is a class extension, then? An anonymous category in the
.mfile (@interface Foo ()) for private declarations and properties; unlike a category it can add instance variables. - Why does the runtime allow this at all? The method table is data, so splicing is cheap and powers frameworks like Core Data.
Related questions
- 4.2.2 What are associated objects, and when are they the right (or wrong) tool?
- 4.6.2 Why is method swizzling considered a footgun, and what do you do instead?
- 4.3.1 What does NSObject provide, and when should a Swift class inherit from it?
What are associated objects, and when are they the right (or wrong)… →