What does NSObject provide, and when should a Swift class inherit from it?

4.3.1 · L2 · Interview

Predict first

What does this print?

class Plain {}
class Cocoa: NSObject {}
let a: Any = Plain()
let b: Any = Cocoa()
print(a is NSObject, b is NSObject)
  • true true
  • false true
  • false false

Mental model. NSObject is the membership card for the Cocoa club. Without it a class works fine in Swift but the club’s machinery will not recognise it.

NSObject is the root of the Cocoa hierarchy. It supplies the runtime interface: isEqual: and hash, description, introspection (isKind(of:), responds(to:), perform), the retain and release machinery, and the hooks behind KVC and KVO. Subclassing it is what makes a class usable by Objective-C and by APIs that expect objects: target-action, KVO, NSCoding, Interface Builder, and many delegate protocols.

A plain Swift class does not inherit from NSObject; its root is an internal Swift base object. Inheriting costs something: a larger object, Objective-C metadata for every @objc member, calls routed through objc_msgSend where they are dynamic, and isEqual semantics you must get right. So do it only when you need the interop. Swift’s is and as? work on both, while isKind(of:) and isMember(of:) are the runtime equivalents.

Only the class that inherits from NSObject is one. A plain Swift class has its own internal root.

Common traps

  • Inheriting from NSObject “just in case”. You pay in size and dispatch for features you do not use.
  • Overriding == instead of isEqual:. Cocoa collections call isEqual: and hash.

They might follow up with

  • Which framework features really require it? KVO, target-action selectors, NSCoding, and anything declared as an Objective-C protocol with class requirements.
  • Is a Swift struct ever an NSObject? No. Value types bridge to Foundation classes when needed, as covered in the bridging section.

Related questions

  • 2.9.5 How does equality work for NSObject subclasses, and why do two “equal” objects stay distinct in a Set?
  • 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?
  • 2.4.5 How does KVO work in Swift, and why does it sometimes never fire?

What is a class cluster, and what does it change for subclassing and… →