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:
src/mulle-objc-fastenumeration.h– Declares themulle_objc_enumeration_mutationfunction prototype.src/mulle-objc-fastenumeration.c– Implements the mutation-abort helper that terminates the program on concurrent modification.test-compiler/fastenumeration/fastenumeration.m– Provides a complete working example of a class implementingNSFastEnumerationwith proper state management.include/mulle-objc-runtime/mulle-objc-runtime.h– Exposes the public API consumed by fast-enumerable collections.src/mulle-objc-runtime-standalone.c– Contains the core method-dispatch engine that forwards thecountByEnumeratingWithState:objects:count:selector to the object's implementation.
Summary
- Mulle-ObjC fast enumeration relies on the
NSFastEnumerationprotocol and thecountByEnumeratingWithState:objects:count:method signature. - The compiler transforms
for-inloops intowhile-loops managing anNSFastEnumerationStatestructure and a temporary object buffer. - Mutation detection uses the
mulle_objc_enumeration_mutationfunction insrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →