MulleObjCRuntimeObject Protocol: Requirements and Purpose in MulleObjC
The MulleObjCRuntimeObject protocol defines the mandatory lifecycle and thread-safety contract that every object managed by the MulleObjC runtime must implement, encompassing retain-release semantics, Thread-Affinity Object (TAO) strategies, and root-object bridging capabilities.
The MulleObjCRuntimeObject protocol serves as the foundational interface in the mulle-objc/mulleobjc repository, governing how objects interact with the runtime for memory management and concurrent access. Defined in src/protocol/MulleObjCRuntimeObject.h, this protocol abstracts the fundamental requirements for reference counting, thread validation, and object-graph handling that the runtime depends on to manage object lifecycles safely across multiple threads.
Core Purpose of the MulleObjCRuntimeObject Protocol
The protocol fulfills four critical functions within the MulleObjC runtime architecture:
Unified Object Lifecycle — It guarantees that every object can respond to retain, release, and retainCount messages, enabling the runtime’s reference-counting garbage collection system to track object lifetimes consistently.
Thread-Safety Integration — The protocol supplies methods for validating and managing safe access across threads, which is essential for MulleObjC’s Thread-Affinity Object (TAO) strategy. Methods like mulleIsThreadSafe and mulleIsAccessibleByThread: allow the runtime to verify that cross-thread object transfers occur safely.
Object-Graph Support — The protocol provides the _becomeRootObject hook, which the runtime invokes when an object becomes the root of an object graph. This allows special handling during serialization, garbage collection cycles, or graph traversal operations.
Debug and Validation — During development, methods such as mulleIsAccessible and mulleIsThreadSafe enable both developers and runtime assertions to verify correct usage patterns and catch threading violations early.
Required Methods and Thread-Safety Contract
As specified in src/protocol/MulleObjCRuntimeObject.h, adopting classes must implement the following methods to satisfy the runtime’s core requirements:
Memory Management Primitives — The protocol mandates retain, release, and retainCount methods, all marked with the MULLE_OBJC_THREADSAFE_METHOD annotation. This annotation expands to __attribute__((annotate("objc_user_4"))), informing the compiler and static analysis tools that these implementations must be safe for concurrent invocation.
Lifecycle Termination — Classes must implement dealloc for instance destruction when the retain count reaches zero. The protocol also declares finalize as a legacy finalizer hook, though it remains unused in Automatic Reference Counting (ARC) environments.
Root Object Bridging — The _becomeRootObject method serves as a callback when the runtime identifies an object as the root of a specific object graph, enabling specialized handling for persistence or garbage collection roots.
Thread-Affinity Control — The protocol requires mulleIsThreadSafe to return a BOOL indicating whether the object permits access from arbitrary threads. For per-thread validation, mulleIsAccessibleByThread: accepts an NSThread parameter to check accessibility against specific execution contexts.
Access Transfer Protocol — When transferring objects between threads, the runtime calls mulleGainAccess on the receiving thread and mulleRelinquishAccess on the sending thread. Variants such as mulleGainAccessWithTAOStrategy: and mulleRelinquishAccessWithTAOStrategy: allow explicit specification of transfer strategies. For batch operations, mulleGainAccessWithUniquingSet: and mulleRelinquishAccessWithUniquingSet: accept a struct mulle_pointerset * parameter to prevent duplicate transfers within the same collection.
Strategy Introspection — Implementers must provide mulleTAOStrategy, which returns a MulleObjCTAOStrategy value indicating the object’s current thread-affinity configuration.
Thread-Affinity Object (TAO) Strategies
The MulleObjCTAOStrategy enumeration, defined alongside the protocol, specifies eight distinct strategies controlling how objects transfer between threads. These range from MulleObjCTAOKnownThreadSafe (indicating the object requires no special handling) to aggressive cleanup variants like MulleObjCTAOCallerRemovesFromAllPools.
When implementing mulleGainAccessWithTAOStrategy: or mulleRelinquishAccessWithTAOStrategy:, classes select the appropriate strategy based on their internal synchronization capabilities. Thread-safe immutable objects typically return MulleObjCTAOKnownThreadSafe from mulleTAOStrategy, while mutable objects bound to specific threads use strategies that ensure proper pool management during transfers.
Practical Implementation Examples
Minimal Thread-Unsafe Object Implementation
The following implementation in src/class/MulleObject.h demonstrates the basic contract for a non-thread-safe object:
#import "MulleObjCRuntimeObject.h"
#import "MulleObjCThreadSafe.h"
@interface SimpleObject : NSObject <MulleObjCRuntimeObject>
{
NSUInteger _retainCount;
}
@end
@implementation SimpleObject
- (instancetype)retain MULLE_OBJC_THREADSAFE_METHOD
{
_retainCount++;
return self;
}
- (void)release MULLE_OBJC_THREADSAFE_METHOD
{
if (--_retainCount == 0)
[self dealloc];
}
- (NSUInteger)retainCount MULLE_OBJC_THREADSAFE_METHOD
{
return _retainCount;
}
- (void)dealloc
{
NSLog(@"SimpleObject dealloc");
[super dealloc];
}
- (BOOL)mulleIsThreadSafe MULLE_OBJC_THREADSAFE_METHOD
{
return NO;
}
- (void)mulleGainAccess MULLE_OBJC_THREADSAFE_METHOD
{
// Default behavior for thread-unsafe objects
}
- (void)mulleRelinquishAccess MULLE_OBJC_THREADSAFE_METHOD
{
// Retain for the receiving thread
[self retain];
}
@end
Thread-Safe Object with Explicit TAO Strategy
Objects that guarantee internal thread safety implement the protocol with optimized strategies:
@interface SafeObject : NSObject <MulleObjCRuntimeObject>
@end
@implementation SafeObject
- (instancetype)retain MULLE_OBJC_THREADSAFE_METHOD
{
return [super retain];
}
- (void)release MULLE_OBJC_THREADSAFE_METHOD
{
[super release];
}
- (NSUInteger)retainCount MULLE_OBJC_THREADSAFE_METHOD
{
return [super retainCount];
}
- (BOOL)mulleIsThreadSafe MULLE_OBJC_THREADSAFE_METHOD
{
return YES;
}
- (MulleObjCTAOStrategy)mulleTAOStrategy MULLE_OBJC_THREADSAFE_METHOD
{
return MulleObjCTAOKnownThreadSafe;
}
@end
Cross-Thread Object Transfer
The runtime uses the access transfer methods when moving objects between threads:
// Thread A (Sender)
SimpleObject *obj = [SimpleObject new];
[obj mulleRelinquishAccessWithTAOStrategy:MulleObjCTAOCallerRemovesFromCurrentPool];
// Object passed to Thread B via queue or message
// Thread B (Receiver)
[obj mulleGainAccessWithTAOStrategy:MulleObjCTAOCallerRemovesFromCurrentPool];
// Object now safe to use in Thread B
Summary
- The
MulleObjCRuntimeObjectprotocol insrc/protocol/MulleObjCRuntimeObject.hdefines the mandatory interface between objects and the MulleObjC runtime. - All implementations must provide thread-safe
retain,release, andretainCountmethods annotated withMULLE_OBJC_THREADSAFE_METHOD. -The protocol enables the Thread-Affinity Object (TAO) system throughmulleGainAccessandmulleRelinquishAccessmethod pairs. - Objects declare their thread-safety capabilities via
mulleIsThreadSafeandmulleTAOStrategy, allowing the runtime to optimize cross-thread transfers. - The
_becomeRootObjecthook provides integration points for garbage collection and object-graph management.
Frequently Asked Questions
What is the difference between mulleGainAccess and mulleRelinquishAccess?
mulleRelinquishAccess is invoked on the sending thread before an object transfers to another thread, allowing the object to prepare for the handoff—typically by retaining itself or removing itself from thread-local autorelease pools. mulleGainAccess is called on the receiving thread after the transfer completes, enabling the object to re-establish thread-local invariants or add itself to the new thread’s pool management structures.
When should an object return YES from mulleIsThreadSafe?
An object should return YES only if its internal state is protected by synchronization primitives (such as mutexes or atomic operations) that guarantee safe concurrent access from multiple threads simultaneously. Immutable objects and those using lock-free data structures typically qualify for this designation, while objects with thread-affine mutable state should return NO.
What is the purpose of the _becomeRootObject method?
The runtime calls _becomeRootObject when an object is identified as the entry point of an object graph, such as during serialization or when establishing garbage collection roots. Implementations can use this hook to perform special initialization, register with external tracking systems, or allocate additional resources required for root-level object management.
How do I choose the correct MulleObjCTAOStrategy for my class?
Select MulleObjCTAOKnownThreadSafe if your class guarantees thread safety through internal synchronization. Use MulleObjCTAOCallerRemovesFromCurrentPool or similar pool-management strategies for thread-affine objects that require explicit cleanup of autorelease pool memberships during transfers. The strategy returned by mulleTAOStrategy must align with the implementation logic in your mulleGainAccessWithTAOStrategy: and mulleRelinquishAccessWithTAOStrategy: methods.
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 →