The NSObject Class Hierarchy Explained
Open any Objective-C file and you'll see it: @interface MyClass : NSObject. That inheritance isn't optional ceremony—it's what makes your class work with the entire Cocoa ecosystem. Let's explore what NSObject actually provides.
The Root of Everything
NSObject is Objective-C's root class. Almost every class you interact with inherits from it, forming a single inheritance tree:
NSObject
├── NSString
├── NSArray
├── UIView
│ ├── UILabel
│ ├── UIButton
│ └── UITableView
├── UIViewController
│ ├── UITableViewController
│ └── UINavigationController
└── YourCustomClass
When you create a class inheriting from NSObject, you get a remarkable amount of functionality for free.
Identity and Comparison
NSObject provides the fundamental notion of object identity:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
The default isEqual: returns YES only when comparing the same instance. Override it for value semantics:
@implementation Person
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if (![object isKindOfClass:[Person class]]) return NO;
Person *other = (Person *)object;
return [self.name isEqualToString:other.name] &&
self.age == other.age;
}
- (NSUInteger)hash {
return self.name.hash ^ @(self.age).hash;
}
@end
Always override both or neither. Collections use hash for quick lookups and isEqual for confirmation.
Introspection
NSObject lets objects examine themselves at runtime:
NSString *str = @"Hello";
[str class]; // NSString (or a private subclass)
[str superclass]; // NSObject
[str isKindOfClass:[NSString class]]; // YES
[str isMemberOfClass:[NSString class]]; // Maybe NO (class clusters)
[str respondsToSelector:@selector(length)]; // YES
[str conformsToProtocol:@protocol(NSCopying)]; // YES
These methods enable dynamic behavior. You can write code that adapts to objects it knows nothing about at compile time.
Memory Management
The retain/release methods live on NSObject:
- (instancetype)retain;
- (oneway void)release;
- (instancetype)autorelease;
- (NSUInteger)retainCount; // Never use this
Under ARC you don't call these directly, but they still execute. Your object exists because NSObject's infrastructure tracks references and deallocates when counts reach zero.
Description for Debugging
When you log an object, NSObject's description methods provide the output:
- (NSString *)description;
- (NSString *)debugDescription;
Override these for useful logging:
- (NSString *)description {
return [NSString stringWithFormat:@"<Person: %@, age %ld>",
self.name, (long)self.age];
}
Now NSLog(@"%@", person) shows something meaningful.
Key-Value Coding
NSObject provides the KVC infrastructure:
id value = [person valueForKey:@"name"];
[person setValue:@"Alice" forKey:@"name"];
// Key paths work too
id city = [person valueForKeyPath:@"address.city"];
This powers Interface Builder bindings, Core Data, and numerous frameworks that access properties by name.
Key-Value Observing
Built on KVC, KVO lets you observe property changes:
[person addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(@"Name changed to: %@", change[NSKeyValueChangeNewKey]);
}
Remove observers before deallocation—this is a common source of crashes.
Initialization
NSObject defines the initialization pattern:
- (instancetype)init {
self = [super init];
if (self) {
// Initialize instance variables
}
return self;
}
The self = [super init] pattern exists because initializers can return different objects (class clusters) or nil (failure). Always check before proceeding.
Copying
The copy methods are declared in NSObject:
- (id)copy;
- (id)mutableCopy;
These call copyWithZone: and mutableCopyWithZone: from NSCopying and NSMutableCopying protocols. Implement those protocols to make your class copyable.
The NSObject Protocol
Interestingly, NSObject the class conforms to NSObject the protocol. The protocol declares the basic methods every object should have:
@protocol NSObject
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
- (Class)superclass;
- (Class)class;
- (instancetype)self;
- (BOOL)respondsToSelector:(SEL)aSelector;
// ... more methods
@end
This means NSProxy (the only other root class) can also provide these methods despite not inheriting from NSObject.
NSProxy: The Other Root
NSProxy exists for objects that forward messages elsewhere:
@interface NSProxy <NSObject> // Conforms to protocol, doesn't inherit
It's used for lazy loading, distributed objects, and test mocks. You rarely encounter it directly.
Objects Without NSObject
Technically, you can create classes that don't inherit from NSObject:
@interface Standalone {
Class isa;
}
@end
But you'd need to implement retain/release yourself, couldn't use KVC or KVO, and would break most frameworks. There's no practical reason to do this.
The Foundation Connection
NSObject lives in Foundation, not the Objective-C runtime itself. This means:
- Pure C programs using the runtime don't need Foundation
- The runtime provides primitives; NSObject provides conveniences
- Other frameworks (like CoreFoundation) can provide alternative base classes
In practice, everything uses Foundation and everything inherits from NSObject. It's the invisible foundation beneath all your code.