What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?

4.4.1 · L2 · Interview

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 runtimeone memberevery memberone member, with this nameno change by itself
Changes Swift call dispatchnononoyes, goes through objc_msgSend
Needed for#selector, target-actionmany selectors at oncea specific ObjC nameKVO, swizzling

Common traps

  • Assuming an NSObject subclass’s methods are all visible to ObjC. They are not; mark them or use @objcMembers.
  • Marking a method @objc and expecting KVO or swizzling to work. You also need dynamic.

They might follow up with

  • What are the two -Swift.h and 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.

Related questions

  • 4.1.4 What is isa, and why does your object’s runtime class sometimes differ from its Swift type?
  • 4.6.1 How does method swizzling work, and why does it need dynamic in Swift?
  • 4.8.2 What does mixing Swift and Objective-C cost, and how do you keep the cost down?

Which Swift features cannot be seen from Objective-C? →