How to Interact with the Core mulle-objc Runtime from MulleObjC Code
Include the mulle-objc.h umbrella header to access zero-overhead inline wrappers like MulleObjCObjectPerformSelector* and MulleObjCObjectGetBOOL for direct runtime manipulation.
The mulle-objc runtime is a lightweight, high-performance engine powering the MulleObjC language. Unlike traditional Objective-C runtimes that hide internals behind complex abstraction layers, mulle-objc exposes its core functionality directly through simple C headers. When you need to interact with the core mulle-objc runtime from MulleObjC code, you work with static inline functions that compile down to raw runtime calls with no linking overhead.
Accessing Runtime Functions via the Umbrella Header
Include the Runtime Header
All runtime entry points flow through src/mulle-objc.h. This umbrella header exports the complete API surface, from low-level dispatch functions to high-level convenience wrappers.
#include "mulle-objc.h"
Available Runtime Capabilities
Including this header grants access to four primary categories of runtime interaction:
- Message sending helpers –
MulleObjCObjectPerformSelector0,MulleObjCObjectPerformSelector1, and variants up toMulleObjCObjectPerformSelector2for dispatching methods with object arguments, plus primitive-specific variants likeMulleObjCObjectPerformSelectorDoubleArgument. - Class and selector introspection –
MulleObjCClassGetID,MulleObjCClassImplementsSelector, andMulleObjCLookupClassByNameUTF8Stringfor runtime type discovery. - Object utilities –
MulleObjCObjectGetClass,MulleObjCInstanceSetClass, andMulleObjCInstanceConstantifyfor direct instance manipulation. - Primitive accessors –
MulleObjCObjectGetBOOL,MulleObjCObjectSetInt, and related functions that handle boxing/unboxing automatically.
All wrappers live in src/function/MulleObjCFunctions.h and remain static inline, ensuring the compiler optimizes away the abstraction layer entirely.
Common Runtime Operations
Dynamic Class and Selector Lookup
Before sending messages dynamically, obtain the Class and SEL identifiers through UTF-8 string lookups. These functions search the runtime's class and selector tables without requiring compile-time knowledge of the types.
Class MyClass = MulleObjCLookupClassByNameUTF8String("MyClass");
SEL sel = MulleObjCCreateSelectorUTF8String("doSomething:");
MulleObjCLookupClassByNameUTF8String queries the global class table, while MulleObjCCreateSelectorUTF8String registers the selector in the selector table if absent.
Sending Messages with Arguments
The runtime provides variadic-style wrappers for message sending. For simple no-argument selectors:
id result = MulleObjCObjectPerformSelector0(anObject, sel);
For two object arguments:
id result = MulleObjCObjectPerformSelector2(anObject, sel, arg1, arg2);
When passing primitive values like doubles or integers, use specialized wrappers that handle the meta-ABI automatically:
SEL sel = MulleObjCCreateSelectorUTF8String("setScale:");
MulleObjCObjectPerformSelectorDoubleArgument(obj, sel, 1.5);
Runtime Introspection and Mutation
Check whether a class implements a method directly (excluding inherited methods) using MulleObjCClassImplementsSelector:
if (MulleObjCClassImplementsSelector(MyClass, sel))
NSLog(@"MyClass implements %@", MulleObjCSelectorUTF8String(sel));
For advanced scenarios like test harnesses or proxy implementations, mutate an instance's class identity directly:
MulleObjCInstanceSetClass(anObject, MyClass);
This operation, declared near line 75 in src/function/MulleObjCFunctions.h, modifies the object's isa pointer immediately.
Architectural Layers
High-Level Wrappers vs. Low-Level Runtime API
The codebase maintains a strict separation between convenience and control:
-
High-level wrappers (
MulleObjCObjectPerformSelector*,MulleObjCObjectGetBOOL) suit everyday code needing primitive value handling or fixed-argument dispatch. These inline functions reside insrc/function/MulleObjCFunctions.hand call into the runtime internally. -
Low-level runtime functions (
mulle_objc_object_call,mulle_objc_class_search_method) provide raw access to method table walks, caching strategies, and fast-path optimizations. These are declared inmulle-objc-runtime/mulle-objc-runtime.hand useful for custom method resolution or metaprogramming.
Both layers remain accessible simultaneously; the compiler inlines the wrappers, so you pay no penalty for choosing the ergonomic API.
Key Source Files for Runtime Interaction
Understanding the repository structure helps navigate the runtime surface:
src/mulle-objc.h– The primary umbrella header that re-exports all runtime capabilities and convenience wrappers.src/function/MulleObjCFunctions.h– Contains the static inline implementations ofMulleObjCObjectPerformSelector*and primitive accessors (lines 33-80).mulle-objc-runtime/mulle-objc-runtime.h– Exposes the underlyingmulle_objc_*functions handling dispatch, class tables, and selector tables.src/class/NSObject.h– Demonstrates how the runtime hooks integrate with root class definitions.src/protocol/NSObjectProtocol.h– Defines protocol metadata used during method lookup.
Practical Code Examples
Example 1 – Dispatch with Primitive Arguments
#import "mulle-objc.h"
void sendDoubleMessage(id obj)
{
SEL sel = MulleObjCCreateSelectorUTF8String("setScale:");
// Wrapper handles meta-ABI for double argument automatically
MulleObjCObjectPerformSelectorDoubleArgument(obj, sel, 1.5);
}
Implementation located in src/function/MulleObjCFunctions.h, lines 73-80.
Example 2 – Verify Class-Specific Method Implementation
#import "mulle-objc.h"
BOOL classImplementsFoo(Class cls)
{
SEL sel = @selector(foo:);
return MulleObjCClassImplementsSelector(cls, sel);
}
Implementation located in src/function/MulleObjCFunctions.h, lines 33-55.
Example 3 – Runtime Class Lookup and Invocation
#import "mulle-objc.h"
void invokePrint(id obj)
{
Class Printer = MulleObjCLookupClassByNameUTF8String("Printer");
SEL selPrint = MulleObjCCreateSelectorUTF8String("print");
if (Printer && MulleObjCClassImplementsSelector(Printer, selPrint))
{
// Equivalent to [obj print];
MulleObjCObjectPerformSelector0(obj, selPrint);
}
}
Example 4 – Advanced Instance Reclassification
#import "mulle-objc.h"
void reclassifyObject(id obj)
{
Class NewCls = MulleObjCLookupClassByNameUTF8String("NewClass");
if (NewCls)
MulleObjCInstanceSetClass(obj, NewCls);
}
Summary
- Include
mulle-objc.hto gain immediate access to all runtime functions without additional linking steps. - Use static inline wrappers like
MulleObjCObjectPerformSelector*andMulleObjCObjectGetBOOLfor zero-overhead message sending and primitive access. - Lookup classes dynamically with
MulleObjCLookupClassByNameUTF8Stringand selectors withMulleObjCCreateSelectorUTF8Stringwhen compile-time types are unavailable. - Introspect capabilities via
MulleObjCClassImplementsSelectorbefore dispatching to avoid runtime exceptions. - Modify instance classes directly using
MulleObjCInstanceSetClassfor low-level object metamorphosis.
Frequently Asked Questions
What is the difference between MulleObjCObjectPerformSelector0 and mulle_objc_object_call?
MulleObjCObjectPerformSelector0 is a static inline wrapper defined in src/function/MulleObjCFunctions.h that provides a type-safe, ergonomic interface for sending messages with no arguments. It internally calls mulle_objc_object_call from mulle-objc-runtime/mulle-objc-runtime.h, which performs the actual method table lookup and dispatch. The wrapper adds no runtime overhead because the compiler inlines it, but it offers cleaner syntax and automatic handling of the meta-ABI.
Do I need to link against a separate runtime library to use these functions?
No. All convenience wrappers in mulle-objc.h are static inline functions defined in headers. They compile directly into your object code, requiring no additional linking beyond the standard mulle-objc runtime. The underlying runtime functions they call are already linked through the standard MulleObjC environment.
How does MulleObjCClassImplementsSelector differ from respondsToSelector:?
MulleObjCClassImplementsSelector checks only the specific class's method table, excluding inherited implementations, whereas respondsToSelector: traverses the entire inheritance hierarchy. Use the former when you need to know if a class defines a method itself, such as when checking for required protocol implementations or avoiding inherited behavior.
Can I use these runtime functions in regular C code, or only in Objective-C .m files?
You can use these functions in any C or Objective-C source file. The mulle-objc runtime API is pure C, and the wrappers in mulle-objc.h are compatible with both .c and .m extensions. This allows you to write runtime introspection logic in C modules while maintaining interoperability with MulleObjC objects.
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 →