# NSObject vs NSProxy in MulleObjC: Fundamental Differences Between Root Classes

> Explore the core differences between NSObject and NSProxy root classes in MulleObjC. Understand stateful object management versus stateless message forwarding for your Objective-C development.

- Repository: [mulle-objc/mulleobjc](https://github.com/mulle-objc/mulleobjc)
- Tags: deep-dive
- Published: 2026-03-07

---

**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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`:

```text
// 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:

- **`NSObject`** implements the full object machinery including reference counting, allocation, and initialization.
- **`NSProxy`** adopts only the `NSObject` protocol (declared as `@interface NSProxy < MulleObjCRootObject, NSObject>`) without inheriting from the `NSObject` class, 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`](https://github.com/mulle-objc/mulleobjc/blob/main/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:

```objc
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`): Both `NSObject` and `NSProxy` return `NO`, ensuring class objects are never treated as proxies.
- **Instance methods** (`-isProxy`): `NSObject` instances return `YES` by default, while `NSProxy` overrides this to return `YES` for 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`:

```objc
// 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`](https://github.com/mulle-objc/mulleobjc/blob/main/src/class/NSObject.h).

### Implementing a Forwarding Proxy with NSProxy

For remote objects or lazy loading, subclass `NSProxy` to intercept messages:

```objc
// 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**: `NSObject` maintains a retain-count field and supports full instance data storage, while `NSProxy` is stateless with no built-in ivars.
- **Instantiation**: Use `+instance` and standard alloc/init patterns with `NSObject`; `NSProxy` requires manual memory allocation via `malloc` and `objc_setClass`.
- **Forwarding**: `NSObject` provides complete forwarding chain defaults, whereas `NSProxy` mandates subclass implementation of `-forwardInvocation:`.
- **Runtime Identity**: Both classes return `NO` for `+isProxy`, but `NSProxy` instances explicitly return `YES` for `-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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`.