How Fast Enumeration Works in Mulle-ObjC: NSFastEnumeration Protocol and Compiler Internals

Mulle-ObjC implements fast enumeration through the standard NSFastEnumeration protocol, where the compiler transforms for-in loops into optimized C while-loops that repeatedly invoke countByEnumeratingWithState:objects:count:, while the runtime provides the mulle_objc_enumeration_mutation helper to detect and abort on collection mutations during iteration.

Fast enumeration provides optimized, safe iteration over collections in Objective-C. The mulle-objc/mulle-objc-runtime repository implements this mechanism exactly like the Apple Objective-C runtime, relying on protocol adoption and compiler-generated loop structures rather than runtime-provided enumerators. This approach delegates the actual batching logic to the collection classes while centralizing mutation safety checks within the runtime core.

The NSFastEnumeration Protocol

Collections that support fast enumeration in Mulle-ObjC must adopt the NSFastEnumeration protocol and implement a single required method. This method is responsible for returning batches of objects to the caller and maintaining iteration state across multiple invocations.

The method signature follows the standard Objective-C convention:

- (uintptr_t) countByEnumeratingWithState:(NSFastEnumerationState *)state
                                 objects:(id *)buffer
                                   count:(uintptr_t)len;

The state parameter tracks the current position in the collection, the buffer provides temporary storage for object pointers, and len indicates the maximum number of objects the buffer can hold. Implementations must update state->itemsPtr to point to the buffer and return the actual number of objects placed in the buffer.

Compiler Transformation of for-in Loops

The Mulle-ObjC compiler (mulle-objc-clang) does not rely on runtime magic to execute for-in syntax. Instead, it lowers the Objective-C syntax into equivalent C code that manages an NSFastEnumerationState structure and a temporary buffer.

A loop such as:

for (Bar *bar in foo)
    [bar print];

Transforms roughly into the following pseudo-code pattern:

NSFastEnumerationState state = {0};
Bar *buffer[16];
uintptr_t count;
while ((count = [foo countByEnumeratingWithState:&state
                                          objects:buffer
                                            count:sizeof(buffer)/sizeof(*buffer)]) != 0)
{
    for (uintptr_t i = 0; i < count; ++i)
    {
        Bar *bar = (Bar *)buffer[i];
        [bar print];
    }
}

This generated code repeatedly calls the collection's countByEnumeratingWithState:objects:count: method until it returns zero, indicating that no objects remain. The buffer size of 16 is typical, though the compiler may choose different sizes based on optimization settings.

Mutation Detection and Safety

Fast enumeration must abort if the underlying collection mutates during iteration. Mulle-ObjC provides a dedicated runtime function to handle this failure scenario, ensuring consistent error reporting across all collection types.

The runtime exposes mulle_objc_enumeration_mutation in src/mulle-objc-fastenumeration.h and implements it in src/mulle-objc-fastenumeration.c:

MULLE_C_NO_RETURN void
   mulle_objc_enumeration_mutation( void *collection)
{
   mulle_objc_universe_fail_inconsistency(
        NULL,
        "collection %p mutated while enumerating",
        collection);
}

When a collection's implementation detects that its internal structure has changed (typically by comparing a stored mutations value against state->mutationsPtr), it invokes this function. The runtime immediately aborts the program with a clear "enumeration mutated" error message, preventing undefined behavior from inconsistent iteration state.

Concrete Implementation Example

The repository includes a functional demonstration in test-compiler/fastenumeration/fastenumeration.m, showing how to properly implement NSFastEnumeration with mutation tracking.

The example implementation fills a buffer with references to self and sets up the mutations pointer for safety detection:

- (uintptr_t) countByEnumeratingWithState:(NSFastEnumerationState *)rover
                                 objects:(id *)buffer
                                   count:(uintptr_t)len
{
    uintptr_t remain = 20 - rover->state;
    if (!remain) return 0;
    if (remain < len) len = remain;

    rover->state    += len;
    rover->itemsPtr  = buffer;
    for (id *end = buffer + len; buffer < end; ++buffer) *buffer = self;
    rover->mutationsPtr = &rover->extra[4];   // mutation detection
    return len;
}

This implementation returns batches of objects until exhausting a fixed set of 20 items, assigning the mutations pointer to enable runtime mutation checks. If the test modifies the collection during the for-in loop, the program triggers mulle_objc_enumeration_mutation and aborts.

Key Source Files

Understanding fast enumeration in Mulle-ObjC requires familiarity with these specific source locations:

Summary

  • Mulle-ObjC fast enumeration relies on the NSFastEnumeration protocol and the countByEnumeratingWithState:objects:count: method signature.
  • The compiler transforms for-in loops into while-loops managing an NSFastEnumerationState structure and a temporary object buffer.
  • Mutation detection uses the mulle_objc_enumeration_mutation function in src/mulle-objc-fastenumeration.c, which aborts execution if the collection changes during iteration.
  • The runtime delegates actual enumeration logic to the collection's protocol implementation rather than providing a built-in enumerator class.

Frequently Asked Questions

What protocol must collections adopt to support fast enumeration in Mulle-ObjC?

Collections must adopt the NSFastEnumeration protocol and implement the countByEnumeratingWithState:objects:count: method. This method receives a state structure and buffer pointer, returning the number of objects placed in the buffer for each batch.

How does Mulle-ObjC detect mutations during fast enumeration?

The generated loop code monitors a mutationsPtr within the NSFastEnumerationState structure. If the collection detects internal changes during iteration, it triggers the mulle_objc_enumeration_mutation function defined in src/mulle-objc-fastenumeration.c, causing the runtime to abort with an inconsistency error.

Does the Mulle-ObjC runtime provide the fast enumeration loop implementation?

No, the runtime does not contain a special enumerator implementation. The compiler generates the loop structure, and the runtime simply dispatches the countByEnumeratingWithState:objects:count: selector to the object's implementation, providing only the mutation detection helper for safety.

Where can I find example implementations of NSFastEnumeration in the Mulle-ObjC repository?

The test-compiler/fastenumeration/fastenumeration.m file contains a complete working example demonstrating a fast-enumerable class with proper state management, buffer filling, and mutation pointer setup for concurrent modification detection.

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 →