What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?
Predict first
What does this print?
class Foo: NSObject {
@objc(renamed) func f() {}
func g() {}
@objc func h() {}
}
print(NSStringFromSelector(#selector(Foo.f)), Foo().responds(to: NSSelectorFromString("g")), Foo().responds(to: NSSelectorFromString("h")))
- f false true
- renamed false true
- renamed true true
Predict first
What does this print?
@objcMembers class Foo: NSObject {
func g() {}
}
print(Foo().responds(to: NSSelectorFromString("g")))
- true
- false
Mental model. @objc lists a method in the Objective-C phone book. dynamic says “always ring through the switchboard”, never dial directly.
@objc makes a declaration visible to the Objective-C runtime: it gets a selector, appears in the generated -Swift.h header, and can be the target of #selector, KVC and target-action. It does not change how Swift code calls it: a normal Swift call still uses a vtable or a direct call. @objcMembers on a class applies @objc to every member at once. @objc(name) sets the exact Objective-C name or selector (@objc(renamed) func f()). dynamic is the one that changes dispatch: it forces every call to go through objc_msgSend, which is what KVO and swizzling need. @nonobjc opts a member out.
Since Swift 4, members of an NSObject subclass are not implicitly @objc (only overrides of ObjC methods and protocol witnesses are), so a selector you expect to exist can be missing: responds(to:) returns false. The costs are real: every @objc member gets a thunk and metadata, adding binary size, so expose the minimum and avoid blanket @objcMembers.
@objc(renamed) sets the selector. g is not exposed, because members of NSObject subclasses are not implicitly @objc.
@objcMembers applies @objc to every member, so the runtime now sees g.
| @objc | @objcMembers | @objc(name) | dynamic | |
|---|---|---|---|---|
| Visible to ObjC and the runtime | one member | every member | one member, with this name | no change by itself |
| Changes Swift call dispatch | no | no | no | yes, goes through objc_msgSend |
| Needed for | #selector, target-action | many selectors at once | a specific ObjC name | KVO, swizzling |
Common traps
- Assuming an
NSObjectsubclass’s methods are all visible to ObjC. They are not; mark them or use@objcMembers. - Marking a method
@objcand expecting KVO or swizzling to work. You also needdynamic.
They might follow up with
- What are the two
-Swift.hand bridging headers? The first lets ObjC see Swift; the second lets Swift see ObjC. See the mixed-module section. - Why do optional protocol requirements need
@objc? Only the Objective-C runtime can ask whether a method is implemented.