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 to MulleObjCObjectPerformSelector2 for dispatching methods with object arguments, plus primitive-specific variants like MulleObjCObjectPerformSelectorDoubleArgument.
  • Class and selector introspection – MulleObjCClassGetID, MulleObjCClassImplementsSelector, and MulleObjCLookupClassByNameUTF8String for runtime type discovery.
  • Object utilities – MulleObjCObjectGetClass, MulleObjCInstanceSetClass, and MulleObjCInstanceConstantify for 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 in src/function/MulleObjCFunctions.h and 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 in mulle-objc-runtime/mulle-objc-runtime.h and 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:

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.h to gain immediate access to all runtime functions without additional linking steps.
  • Use static inline wrappers like MulleObjCObjectPerformSelector* and MulleObjCObjectGetBOOL for zero-overhead message sending and primitive access.
  • Lookup classes dynamically with MulleObjCLookupClassByNameUTF8String and selectors with MulleObjCCreateSelectorUTF8String when compile-time types are unavailable.
  • Introspect capabilities via MulleObjCClassImplementsSelector before dispatching to avoid runtime exceptions.
  • Modify instance classes directly using MulleObjCInstanceSetClass for 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.

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:

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 →