NSObject vs NSProxy in MulleObjC: Fundamental Differences Between Root Classes
NSObject is a stateful root class that manages instance data and reference counting, while NSProxy is a stateless message-forwarding root class designed to intercept and delegate messages without storing instance variables.
Both NSObject and NSProxy inherit from MulleObjCRootObject in the mulle-objc/mulleobjc runtime, yet they serve opposing architectural purposes. Understanding the distinction between these foundation classes is essential for implementing everything from simple model objects to complex distributed object proxies.
Core Architectural Differences
Design Philosophy and Memory Layout
NSObject implements the complete Objective-C object model, carrying both state and metadata. According to the layout diagram in src/class/NSObject.h (lines 58-71), instances contain a retain-count field at offset -16, an isa pointer at offset -8, and instance data starting at offset 0:
// MulleObjC NSObject layout
-16 | retainCount |
-8 | isa |
0 | … (instance data) |
In contrast, NSProxy is designed as a thin message-forwarding shell. The class declares no built-in instance variables; the object consists essentially of only an isa pointer. This stateless design makes NSProxy lightweight but incapable of storing local data without subclass-specific allocation strategies.
Inheritance and Protocol Conformance
While both classes inherit from MulleObjCRootObject, they diverge in protocol adoption:
NSObjectimplements the full object machinery including reference counting, allocation, and initialization.NSProxyadopts only theNSObjectprotocol (declared as@interface NSProxy < MulleObjCRootObject, NSObject>) without inheriting from theNSObjectclass, ensuring it provides only the minimal interface required for object behavior.
Object Creation and Instantiation Patterns
NSObject Factory Methods
NSObject provides convenient factory methods that handle allocation, initialization, and autorelease in a single call. As defined in src/class/NSObject.h (lines 21-33), these include:
+instance– Returns a new autoreleased instance+object– Synonym for convenience+instantiate– Alternative factory entry point
Typical usage follows the pattern:
MyClass *obj = [MyClass instance]; // Creates, inits, and autoreleases
obj.name = @"Mulle";
NSProxy Allocation Requirements
NSProxy cannot be instantiated using the standard +alloc/-init pattern. The base implementation in src/class/NSProxy.m provides only +isProxy and -isProxy methods. Subclasses must implement custom allocation strategies, typically allocating raw memory with malloc followed by objc_setClass to establish the isa pointer, or by providing specialized factory methods that bypass the normal retain-count infrastructure.
Message Forwarding and Runtime Behavior
Forwarding Chain Implementation
NSObject supplies the complete forwarding infrastructure including -forwardingTargetForSelector:, -forwardInvocation:, and -doesNotRecognizeSelector:. This allows instances to redirect unknown messages to other objects or raise meaningful errors.
NSProxy provides only the minimum forwarding interface. A functional proxy subclass must override -forwardInvocation: (or -forward: in MulleObjC) to define custom delegation logic, as the base class contains no default implementation for handling unrecognized selectors.
isProxy Semantics and Runtime Identification
The runtime distinguishes proxies from regular objects through the isProxy methods implemented in src/class/NSProxy.m (lines 52-61):
- Class methods (
+isProxy): BothNSObjectandNSProxyreturnNO, ensuring class objects are never treated as proxies. - Instance methods (
-isProxy):NSObjectinstances returnYESby default, whileNSProxyoverrides this to returnYESfor instances, marking them explicitly as proxies.
This distinction allows the runtime to treat proxy instances specially (e.g., object_isProxy returns YES for NSProxy descendants), bypassing standard reference counting mechanics since proxies carry no retain-count field.
Practical Implementation Examples
Subclassing NSObject for State Management
When building model objects or controllers that require data storage, subclass NSObject:
// MyObject.h
#import "NSObject.h"
@interface MyObject : NSObject
@property (nonatomic, copy) NSString *name;
- (void) greet;
@end
// MyObject.m
#import "MyObject.h"
@implementation MyObject
- (void) greet {
printf("Hello, %s!\n", [self.name UTF8String]);
}
@end
// Usage
MyObject *obj = [MyObject instance]; // +instance creates an autoreleased object
obj.name = @"Mulle";
[obj greet]; // prints “Hello, Mulle!”
Key source: NSObject provides +instance and the retain-count layout defined in src/class/NSObject.h.
Implementing a Forwarding Proxy with NSProxy
For remote objects or lazy loading, subclass NSProxy to intercept messages:
// ForwardingProxy.h
#import "NSProxy.h"
@interface ForwardingProxy : NSProxy
- (instancetype)initWithTarget:(id)target;
@end
// ForwardingProxy.m
#import "ForwardingProxy.h"
@implementation ForwardingProxy {
id _target; // Stored pointer, not a true ivar of the proxy
}
- (instancetype)initWithTarget:(id)target {
_target = target; // Manual retain/release management required
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:_target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
@end
// Usage
NSObject *real = [NSObject instance];
ForwardingProxy *proxy = [[ForwardingProxy alloc] initWithTarget:real];
[proxy description]; // Forwarded to `real`
Key source: NSProxy defines the isProxy semantics in src/class/NSProxy.m and provides no storage allocation.
Summary
- State Management:
NSObjectmaintains a retain-count field and supports full instance data storage, whileNSProxyis stateless with no built-in ivars. - Instantiation: Use
+instanceand standard alloc/init patterns withNSObject;NSProxyrequires manual memory allocation viamallocandobjc_setClass. - Forwarding:
NSObjectprovides complete forwarding chain defaults, whereasNSProxymandates subclass implementation of-forwardInvocation:. - Runtime Identity: Both classes return
NOfor+isProxy, butNSProxyinstances explicitly returnYESfor-isProxy, allowing the runtime to identify forwarding objects.
Frequently Asked Questions
Can NSProxy store instance variables in MulleObjC?
No, the base NSProxy class declares no instance variables and is designed to remain stateless. While subclasses can manage external storage through associated objects or malloc'd structures, the standard proxy layout defined in src/class/NSProxy.h contains only an isa pointer. This design ensures proxies remain lightweight vessels for message forwarding without the overhead of reference counting fields or inline data storage.
Why does NSObject return YES for -isProxy but NO for +isProxy?
This semantic distinction allows the runtime to differentiate between proxy instances and proxy classes. According to src/class/NSProxy.m (lines 52-61), class objects always return NO for +isProxy, ensuring the class itself is not treated as a forwarding object. The instance method -isProxy returns YES by default in NSObject, establishing a baseline identifier that NSProxy maintains to signal interception capabilities to the runtime.
How do I properly allocate an NSProxy subclass in MulleObjC?
Unlike NSObject subclasses that use [MyProxy alloc], NSProxy derivatives must implement custom allocation logic. Typically, you allocate raw memory using malloc, then use objc_setClass to establish the isa pointer, or implement a static factory method that handles memory without invoking NSObject's retain-count initialization. The base class in src/class/NSProxy.m provides no +alloc implementation, enforcing the contract that proxies manage their own lifecycle.
Which root class should I use for remote object proxies?
Use NSProxy for remote object proxies, lazy loaders, or aspect-oriented programming scenarios where you need to intercept every message without maintaining local state. NSProxy deliberately avoids the storage overhead and reference counting mechanics found in NSObject, making it ideal for forwarding messages to remote targets or delaying object realization. For local model objects requiring property storage and standard Objective-C behavior, inherit from NSObject.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →