How to Implement retain, release, and autorelease Correctly in MulleObjC Mode O

In MulleObjC Mode O, you balance object lifetimes manually by calling -retain, -release, and -autorelease on NSObject, which invoke atomic inline helpers defined in src/function/MulleObjCAllocation.h to update reference counts.

MulleObjC Mode O is the classic manual reference-counting environment in the mulle-objc/mulleobjc runtime, distinct from Automatic Reference Counting (ARC). Unlike compiler-managed ARC, Mode O requires developers to explicitly manage memory using three fundamental primitives that are implemented as runtime inline functions rather than compiler-generated code.

Core Memory Management Primitives

MulleObjC Mode O provides retain, release, and autorelease as methods on NSObject. Each method maps to low-level atomic operations that directly manipulate the object's reference count.

The retain Method

The -retain method is declared in src/class/NSObject.h at lines 61-73. The implementation lives inline in src/function/MulleObjCAllocation.h at lines 424-432, where it calls _mulle_objc_object_retain_inline. This helper atomically increments the object’s retain-count and returns the same object to the caller.

The release Method

Declared in src/class/NSObject.h between lines 94-106, the -release method invokes _mulle_objc_object_release_inline from src/function/MulleObjCAllocation.h (lines 438-446). This function decrements the retain-count and automatically triggers deallocation via the allocator when the count reaches zero.

The autorelease Method

The -autorelease method appears in src/class/NSObject.h at lines 115-126. Its implementation in src/function/MulleObjCAllocation.h (lines 350-358) calls _mulle_objc_object_autorelease through the universal helper MulleObjCAutoreleaseAllocation. This places the object into the current thread’s autorelease pool, which will later send release to the object when the pool is drained.

How Autorelease Pools Function

Autorelease pool mechanics are defined in src/class/NSAutoreleasePool.h and src/class/MulleObjCAutoreleasePool.h. When you create a pool (typically on the stack using @autoreleasepool), it captures objects sent autorelease. Upon draining, the pool iterates its contents and invokes _mulle_objc_object_release_inline on each stored object.

By default, every newly created object returned from [[MyClass alloc] init] is autoreleased unless you explicitly retain it. This behavior is documented in the comments around line 97 of src/class/NSObject.h.

Thread Safety and Zero-Retain Objects

All low-level helpers (_mulle_objc_object_retain_inline, _mulle_objc_object_release_inline, and _mulle_objc_object_autorelease) are atomic and utilize per-universe thread-foundation information defined in src/mulle-objc-universeconfiguration-private.h.

The runtime also supports zero-retain objects for singleton patterns. Allocation helpers in MulleObjCAllocation.h provide variants that do not set an initial retain count of 1. You must call retain manually on these objects to establish ownership.

Correct Implementation Patterns

Use the following pattern to manage object lifecycles in Mode O:

// 1. Create an object – it is autoreleased by default
id obj = [[MyClass alloc] init];

// 2. Keep it beyond the current autorelease pool
[obj retain];

// … use obj …

// 3. When done, balance the retain
[obj release];

// 4. Explicit pool for temporary objects
@autoreleasepool {
    id temp = [[TempClass alloc] init];
    // use temp
}

Each retain call must be balanced by exactly one release call. Objects created within an @autoreleasepool block are released automatically when execution exits the block, so do not send release to them unless you have previously sent retain.

Critical Pitfalls to Avoid

  • Over-retaining: Sending retain without a matching release causes memory leaks. You can detect this via the retainCount method in tests such as test/NSObject/retainCount.m.
  • Double-free errors: Never call release on an object you received as autoreleased and did not retain. The pool will release it automatically, causing a double-free if you release it manually.
  • ARC boundary violations: Manual retain and release calls are only valid in Mode O. Do not use these methods in ARC-enabled compilation units where the compiler inserts _mulle_objc_object_retain_inline and _mulle_objc_object_release_inline automatically.

Summary

  • -retain, -release, and -autorelease are declared in src/class/NSObject.h and implemented as atomic inline wrappers in src/function/MulleObjCAllocation.h.
  • Default object creation returns autoreleased objects; you must call retain to extend an object’s lifetime beyond the current autorelease pool.
  • Thread safety is guaranteed through atomic operations using per-universe thread information from src/mulle-objc-universeconfiguration-private.h.
  • Balance every retain with a release to prevent leaks, and never release objects you do not explicitly own.
  • Zero-retain allocations exist for singleton patterns but require explicit retain calls to establish ownership.

Frequently Asked Questions

What distinguishes MulleObjC Mode O from ARC?

MulleObjC Mode O requires explicit manual management using retain, release, and autorelease method calls, whereas ARC (Automatic Reference Counting) inserts these calls at compile time. In Mode O, you directly control the _mulle_objc_object_retain_inline and _mulle_objc_object_release_inline primitives defined in src/function/MulleObjCAllocation.h.

Where are the retain and release methods actually implemented?

While declared as methods in src/class/NSObject.h, the implementations are inline functions in src/function/MulleObjCAllocation.h. The retain method calls _mulle_objc_object_retain_inline at lines 424-432, and release calls _mulle_objc_object_release_inline at lines 438-446, both operating atomically on the object's reference count.

Can I create objects with no initial retain count?

Yes. MulleObjC supports zero-retain objects through specialized allocation helpers in MulleObjCAllocation.h. These are useful for implementing singleton patterns, but you must call retain manually to take ownership of such objects.

What happens if I forget to retain an autoreleased object?

The object is released when the enclosing autorelease pool drains, typically at the end of the event loop or @autoreleasepool block. Accessing the object afterward results in undefined behavior or crashes. Always send retain to objects you need to keep beyond the current scope, as implemented in src/class/NSAutoreleasePool.h.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →