Understanding the Infraclass and Metaclass Hierarchy in mulle-objc-runtime

The infraclass and metaclass hierarchy in mulle-objc-runtime consists of three C structures—struct _mulle_objc_infraclass for instance layouts, struct _mulle_objc_class as the common base, and struct _mulle_objc_metaclass for class methods—wired together to create parallel inheritance chains for instance and class behavior.

The mulle-objc-runtime reimplements Objective-C's object model using pure C structures while maintaining full fidelity to the traditional runtime semantics. Understanding the infraclass and metaclass hierarchy is essential for anyone extending the runtime, debugging class loading, or implementing low-level Objective-C tooling.

Core Architecture of the Infraclass and Metaclass Hierarchy

The runtime models every logical class as a pair of objects: the infraclass handling instance state and the metaclass handling class behavior. Both share a common base structure but serve distinct roles in the object model.

The Three Core Structures

Three tightly-coupled C structures define the hierarchy:

  • struct _mulle_objc_infraclass — Represents the instance side of a class. It stores object layout, ivars, properties, and instance method lists. Every concrete, instantiable class has an infraclass, and it embeds a struct _mulle_objc_class as its base.

  • struct _mulle_objc_class — Serves as the abstract base holding runtime information common to both infraclasses and metaclasses. This includes the class ID, name pointer, superclass reference, method caches, flags, and a pointer to the corresponding metaclass. Defined in src/mulle-objc-class.h.

  • struct _mulle_objc_metaclass — Represents the class object itself, containing class methods and class variables. It also inherits from struct _mulle_objc_class, but its superclass chain parallels the infraclass inheritance chain.

How the Hierarchy is Wired

The wiring between these structures creates Objective-C's characteristic parallel inheritance system:


infraclass (Foo) ──► struct _mulle_objc_infraclass
   │                                 │
   │ inherits (base)                 │ base = struct _mulle_objc_class
   ▼                                 ▼
struct _mulle_objc_class (Foo class object)
   │                                 │
   │ has → metaclass pointer ──► struct _mulle_objc_metaclass (Foo's metaclass)
   │                                 │
   │ inherits (base)                 │ base = struct _mulle_objc_class
   ▼                                 ▼
struct _mulle_objc_class (metaclass of Foo)

The infraclass's base field contains a standard struct _mulle_objc_class. That base structure's metaclass pointer references the metaclass for the same logical class. Crucially, the metaclass's superclass points to the metaclass of the infraclass's superclass, creating a parallel chain for class-method inheritance that terminates at the root metaclass.

Root of the Hierarchy

Every mulle-objc-runtime universe establishes a root infraclass (typically corresponding to NSObject in standard configurations) and its associated root metaclass. The root infraclass has no superclass, and the root metaclass's superclass pointer is NULL, terminating both inheritance chains.

These root objects are created during universe initialization in src/mulle-objc-universe.c. The function mulle_objc_universe_initialize sets universe->foundation.rootclassid and registers both the root infraclass and root metaclass in the global class tables.

Working with Infraclasses and Metaclasses in Code

When creating or introspecting classes programmatically, you interact with these structures through specific initialization patterns and accessor functions.

Declaring a Class Pair

The following pattern mirrors the runtime's internal class loading mechanism, showing how to explicitly initialize an infraclass and metaclass pair:

/* foo.c – implementation */
#include "mulle-objc-runtime.h"
#include "mulle-objc-infraclass.h"
#include "mulle-objc-metaclass.h"

/* 1. Create the infraclass (instance side) */
static struct _mulle_objc_infraclass  Foo_infra;

/* 2. Create the metaclass (class-object side) */
static struct _mulle_objc_metaclass  Foo_meta;

/* 3. Initialise both structures */
static void Foo_initialize(void)
{
    struct mulle_allocator *allocator = mulle_default_allocator;

    /* initialise the infraclass */
    _mulle_objc_infraclass_plusinit(&Foo_infra, allocator);
    _mulle_objc_class_init(
        _mulle_objc_infraclass_as_class(&Foo_infra),
        "Foo",
        sizeof(struct Foo),
        0,
        mulle_objc_get_classid("Foo"),
        /* superclass = */ &_mulle_objc_universe_get_rootclassid,
        /* universe   = */ &_mulle_objc_universe_get_universe());

    /* initialise the metaclass */
    _mulle_objc_metaclass_plusinit(&Foo_meta, allocator);
    _mulle_objc_class_init(
        _mulle_objc_metaclass_as_class(&Foo_meta),
        "Foo",
        0,
        0,
        mulle_objc_get_classid("Foo"),
        /* superclass = */ _mulle_objc_class_get_metaclass(
                                _mulle_objc_class_get_superclass(
                                   _mulle_objc_infraclass_as_class(&Foo_infra))),
        /* universe   = */ &_mulle_objc_universe_get_universe());

    /* link the two sides together */
    _mulle_objc_class_set_metaclass(_mulle_objc_infraclass_as_class(&Foo_infra),
                                    _mulle_objc_metaclass_as_class(&Foo_meta));
}

The code demonstrates the explicit relationship: both the infraclass and metaclass embed a struct _mulle_objc_class, accessed via the *_as_class casting helpers. The metaclass's superclass is explicitly set to the metaclass of the parent infraclass, maintaining the parallel hierarchy.

Accessing the Metaclass Chain

To traverse the metaclass inheritance chain from user code:

/* Assume we have a `struct Foo *obj` */
struct _mulle_objc_class *cls = mulle_objc_object_get_class((struct _mulle_objc_object *)obj);

/* Get the metaclass that holds class methods */
struct _mulle_objc_metaclass *meta = (struct _mulle_objc_metaclass *)mulle_objc_class_get_metaclass(cls);

/* Walk the metaclass chain */
while (meta)
{
    printf("Metaclass name: %s\n", mulle_objc_class_get_name(&meta->base));
    meta = (struct _mulle_objc_metaclass *)mulle_objc_class_get_superclass(&meta->base);
}

mulle_objc_class_get_metaclass retrieves the metaclass associated with any class object, while mulle_objc_class_get_superclass walks the inheritance chain—whether for infraclasses or metaclasses—depending on the starting point.

Type Checking

The runtime provides predicates to distinguish between infraclasses and metaclasses at runtime:

if (mulle_objc_class_is_infraclass(cls))
    printf("This is an infraclass (instance side)\n");
else if (mulle_objc_class_is_metaclass(cls))
    printf("This is a metaclass (class-object side)\n");

These functions inspect the state bits defined in src/mulle-objc-class.h, allowing safe dispatch based on class type.

Key Helper Functions and APIs

The runtime exposes several inline accessor functions defined in the header files that abstract the casting and navigation between hierarchy levels:

  • _mulle_objc_infraclass_as_class(infra) — Casts an infraclass pointer to its embedded struct _mulle_objc_class base.
  • _mulle_objc_class_get_metaclass(cls) — Retrieves the metaclass associated with a given class or infraclass.
  • _mulle_objc_class_get_superclass(cls) — Walks the infraclass inheritance chain.
  • _mulle_objc_metaclass_get_superclass(meta) — Walks the metaclass inheritance chain, parallel to the corresponding infraclass chain.
  • mulle_objc_universe_register_infraclass(universe, infra) — Registers a newly created infraclass and automatically links its metaclass into the universe's class tables.

These functions are used throughout src/mulle-objc-universe.c during class loading, method lookup, and object allocation.

Summary

  • The infraclass (struct _mulle_objc_infraclass) defines instance layout and instance methods, embedding a base struct _mulle_objc_class.
  • The metaclass (struct _mulle_objc_metaclass) defines class methods and also embeds struct _mulle_objc_class, inheriting from the metaclass of the parent infraclass.
  • The base class structure (struct _mulle_objc_class) contains the metaclass pointer linking the two sides and the superclass pointer establishing inheritance.
  • Root classes are initialized in mulle-objc-universe.c, establishing the termination points for both inheritance chains.
  • Helper functions like _mulle_objc_class_get_metaclass and type predicates abstract the casting required to navigate between these structures.

Frequently Asked Questions

What is the difference between an infraclass and a metaclass in mulle-objc?

An infraclass (struct _mulle_objc_infraclass) represents the template for instance objects, storing ivars, instance methods, and object size. A metaclass (struct _mulle_objc_metaclass) represents the class object itself, storing class methods and metadata. Every logical class has one of each, linked through the base structure's metaclass pointer.

How does the metaclass inheritance chain work?

The metaclass inheritance chain runs parallel to the infraclass chain. If class B inherits from class A, then B's metaclass inherits from A's metaclass. This ensures that class methods follow the same inheritance pattern as instance methods. The chain terminates at the root metaclass, whose superclass is NULL.

Where are the root infraclass and metaclass initialized?

The root infraclass and metaclass are created during universe bootstrap in src/mulle-objc-universe.c. The function mulle_objc_universe_initialize sets up universe->foundation.rootclassid and registers both structures, establishing the foundation for the entire class hierarchy.

How do I check if a struct pointer is an infraclass or metaclass?

Use the runtime's type predicate functions: mulle_objc_class_is_infraclass(cls) returns true for infraclasses, while mulle_objc_class_is_metaclass(cls) returns true for metaclasses. These inspect bit flags in the class's state field defined in src/mulle-objc-class.h.

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 →