# How to Load Classes, Categories, and Methods at Runtime with Mulle‑ObjC‑Runtime

> Discover how Mulle‑ObjC‑Runtime loads classes categories and methods at runtime resolving dependencies and executing load methods before registering entities.

- Repository: [mulle-objc/mulle-objc-runtime](https://github.com/mulle-objc/mulle-objc-runtime)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Mulle‑ObjC‑Runtime loads compiled Objective‑C entities through load‑info structures that are enqueued at program start, resolving dependencies via wait‑queues and executing `+load` methods before registering classes in the active universe.**

Loading classes, categories, and methods at runtime in the Mulle‑ObjC‑Runtime relies on a structured enqueueing system that processes static load‑info metadata generated by the compiler. This guide explains how the runtime in `mulle-objc/mulle-objc-runtime` parses these structures, resolves inter-entity dependencies, and registers entities in the universe, with practical examples you can implement in your own projects.

## Runtime Architecture and Load‑Info Structures

### The Universe Container

A **universe** (`struct _mulle_objc_universe`) represents an isolated Objective‑C runtime instance that stores all loaded entities. According to [`src/mulle-objc-universe.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.h) and [`src/mulle-objc-universe-struct.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-struct.h), the universe maintains:

- **Load bits** (`universe->loadbits`) that mark available runtime features such as TPS (Tagged Pointer Support), TAO (Tagged Argument Optimization), and fast-calls
- **Wait‑queues** (`classestoload` and `categoriestoload`) implemented as concurrent hash maps that temporarily hold load-class or load-category objects when dependencies are missing
- A **call‑queue** (`struct _mulle_objc_callqueue *loads`) where `+load` methods accumulate before execution

You obtain the default universe via `mulle_objc_universe_get()`, which triggers the at-init queue draining process.

### Load‑Info Metadata

When the Mulle‑ObjC compiler translates source code, it emits binary **load‑info** blobs represented at runtime by `struct _mulle_objc_loadinfo` (defined in [`src/mulle-objc-loadinfo.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-loadinfo.h)). As detailed in [`src/mulle-objc-load.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.h), these structures contain:

- `struct _mulle_objc_loadclasslist *classlist` – array of class descriptors
- `struct _mulle_objc_loadcategorylist *categorylist` – array of category descriptors  
- `version.bits` – bit-mask describing compile-time options and optimization levels

## The Loading Pipeline in mulle-objc-load.c

The core loader implementation in [`src/mulle-objc-load.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.c) processes enqueued load-info through six distinct phases:

1. **Parse load‑info** – Iterate over `classlist` and `categorylist` arrays
2. **Resolve dependencies** – Check superclasses via `_mulle_objc_universe_lookup_infraclass()`; store missing dependencies in wait‑queues using `_mulle_objc_map_append_info()`
3. **Queue `+load` methods** – Add class and category `+load` implementations to the `loads` call‑queue via `mulle_objc_callqueue_add()`
4. **Satisfy wait‑queues** – Re-examine pending entries after each registration; process dependencies once unblocked
5. **Execute `+load` queue** – Run accumulated `+load` calls in specification order (superclass before subclass, class before category)
6. **Mark universe bits** – Set flags like `MULLE_OBJC_UNIVERSE_HAVE_TPS_CLASSES` after first successful load

The process remains **single‑threaded** during initial loading, simplifying concurrency management for the lock-less concurrent hash maps.

## Enqueueing Load‑Info in Practice

### Static and Dynamic Enqueueing

The public API entry point `mulle_objc_loadinfo_enqueue_nofail(struct _mulle_objc_loadinfo *)` (declared in [`src/mulle-objc-load.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.h)) registers load-info with the at-init subsystem. For statically linked binaries, the compiler automatically generates these calls. For plugins or dynamic libraries, you must invoke this manually:

```c
#include "mulle-objc-load.h"

extern struct _mulle_objc_loadinfo mymodule_loadinfo;

int main(void)
{
    /* Register the compiled entities */
    mulle_objc_loadinfo_enqueue_nofail(&mymodule_loadinfo);
    
    /* Initialize universe - this triggers the actual loading */
    struct _mulle_objc_universe *universe = mulle_objc_universe_get(NULL);
    
    /* Classes are now registered and +load methods executed */
    return 0;
}

```

### Manual Load‑Info Construction

For runtime code generation, manually populate the structures from [`src/mulle-objc-load.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.h):

```c
/* Method definition */
static struct _mulle_objc_method my_methods[] = {
    { .selectorid = MULLE_OBJC_SELECTORID_init,
      .implementation = (mulle_objc_implementation_t)my_init }
};

static struct _mulle_objc_methodlist my_methodlist = {
    .n_methods = 1,
    .methods   = my_methods
};

/* Class descriptor */
static struct _mulle_objc_loadclass my_class = {
    .classid          = 0x12345678UL,
    .classname        = "MyClass",
    .superclassid     = MULLE_OBJC_NO_CLASSID,
    .instancesize     = sizeof(struct MyClass),
    .instancemethods  = &my_methodlist,
    .origin           = "generated.c"
};

/* Class list wrapper */
static struct _mulle_objc_loadclasslist my_classlist = {
    .n_loadclasses = 1,
    .loadclasses   = { &my_class }
};

/* Final load-info structure */
struct _mulle_objc_loadinfo my_loadinfo = {
    .classlist    = &my_classlist,
    .categorylist = NULL,
    .version.bits = MULLE_OBJC_RUNTIME_LOAD_VERSION
};

```

**Important:** In production code, the compiler generates `classid` and `selectorid` values using `mulle_objc_hash_string()`. Manual construction is primarily useful for dynamic code generation scenarios.

### Loading Categories at Runtime

Categories follow the same enqueueing pattern but target existing classes. The loader finds the target class by `classid` and appends the category's method list:

```c
extern struct _mulle_objc_loadinfo my_category_loadinfo;

/* Enqueue after universe initialization */
mulle_objc_loadinfo_enqueue_nofail(&my_category_loadinfo);

/* The loader will:
   1. Locate the target class via its classid
   2. Append the category's methodlist to the class
   3. Execute the category's +load method immediately if defined
*/

```

## Debugging Runtime Loading

The runtime provides built-in diagnostics via environment variables. Enable tracing to monitor the loading process:

```bash
export MULLE_OBJC_TRACE_LOADINFO=1
export MULLE_OBJC_DEBUG_DEPENDENCY=1
export MULLE_OBJC_WARN_STUCK_LOADABLE=1
./my_program

```

These settings activate debug output in [`src/mulle-objc-load.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.c) that prints:

- Each load‑info object as processed
- Dependency checks for superclasses and categories  
- Warnings for classes or categories remaining in wait‑queues after loading completes

## Complete Working Example

The following program demonstrates defining a class with `+load` and `-init` methods, enqueueing the load-info, and verifying registration:

```c
#include "mulle-objc-load.h"
#include "mulle-objc-runtime.h"
#include <stdio.h>

/* Instance method implementation */
static void *my_init(void *self, mulle_objc_selector_t _cmd)
{
    printf("MyClass init called\n");
    return self;
}

/* +load method implementation */
static void my_load(void *self, mulle_objc_selector_t _cmd)
{
    printf("+load of MyClass executed\n");
}

/* Method lists */
static struct _mulle_objc_method class_methods[] = {
    { .selectorid = MULLE_OBJC_SELECTORID_load,
      .implementation = (mulle_objc_implementation_t)my_load }
};

static struct _mulle_objc_method instance_methods[] = {
    { .selectorid = MULLE_OBJC_SELECTORID_init,
      .implementation = (mulle_objc_implementation_t)my_init }
};

static struct _mulle_objc_methodlist class_methodlist = {
    .n_methods = 1, .methods = class_methods
};

static struct _mulle_objc_methodlist instance_methodlist = {
    .n_methods = 1, .methods = instance_methods
};

/* Load-class descriptor */
static struct _mulle_objc_loadclass my_loadclass = {
    .classid          = 0xA1B2C3D4UL,
    .classname        = "MyClass",
    .superclassid     = MULLE_OBJC_NO_CLASSID,
    .instancesize     = sizeof(void *),
    .classmethods     = &class_methodlist,
    .instancemethods  = &instance_methodlist,
    .origin           = "example.c"
};

static struct _mulle_objc_loadclasslist my_classlist = {
    .n_loadclasses = 1,
    .loadclasses   = { &my_loadclass }
};

/* Load-info structure */
struct _mulle_objc_loadinfo my_loadinfo = {
    .classlist    = &my_classlist,
    .categorylist = NULL,
    .stringlist   = NULL,
    .version.bits = MULLE_OBJC_RUNTIME_LOAD_VERSION
};

int main(void)
{
    /* Enqueue and process load-info */
    mulle_objc_loadinfo_enqueue_nofail(&my_loadinfo);
    
    struct _mulle_objc_universe *universe = mulle_objc_universe_get(NULL);
    
    /* Verify registration */
    struct _mulle_objc_class *cls = mulle_objc_universe_lookup_class_by_name(
                                        universe, "MyClass");
    if (!cls) {
        fprintf(stderr, "Class not found!\n");
        return 1;
    }
    
    /* Create and initialize instance */
    void *obj = mulle_objc_class_alloc_instance(cls);
    obj = mulle_objc_object_msgSend(obj, MULLE_OBJC_SELECTORID_init);
    
    return 0;
}

```

When executed with `MULLE_OBJC_TRACE_LOADINFO=1`, this outputs:

```

+load of MyClass executed
MyClass init called

```

## Summary

- **Load‑info structures** bridge compiled Objective‑C artifacts and the runtime; enqueue them with `mulle_objc_loadinfo_enqueue_nofail()` from [`src/mulle-objc-load.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.h)
- **Dependency resolution** occurs automatically through the universe's wait‑queues (`classestoload` and `categoriestoload`) when superclasses or required categories are initially missing
- **`+load` execution order** is maintained through a dedicated call‑queue, ensuring superclass `+load` runs before subclass `+load`, and class `+load` runs before category `+load`
- **Single‑threaded loading** simplifies concurrency during initialization, transitioning to lock-less concurrent hash maps after registration completes
- **Diagnostic environment variables** (`MULLE_OBJC_TRACE_LOADINFO`, `MULLE_OBJC_DEBUG_DEPENDENCY`) provide visibility into complex dependency graphs during development

## Frequently Asked Questions

### How does the runtime handle missing superclass dependencies during loading?

When [`src/mulle-objc-load.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.c) encounters a class whose superclass is not yet registered, it stores the class descriptor in the universe's `classestoload` wait‑queue via `_mulle_objc_map_append_info()`. After each successful class registration, the loader re‑examines this queue and processes any classes whose dependencies are now satisfied. This continues until the wait‑queue empties or loading completes.

### What is the difference between `mulle_objc_loadinfo_enqueue_nofail()` and automatic loading?

Statically linked binaries automatically enqueue load‑info through the compiler-generated at-init subsystem, requiring no manual intervention. You must explicitly call `mulle_objc_loadinfo_enqueue_nofail()` when loading **plugins**, **dynamic libraries**, or **runtime-generated code** to ensure the runtime processes the new entities before use.

### Can I unload classes or categories after loading them?

Yes. The runtime implements `+unload` methods symmetrical to `+load`. When unregistering a class or category, the loader in [`src/mulle-objc-load.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-load.c) schedules `+unload` invocations through the same call‑queue mechanism, executing them in reverse dependency order (categories before classes, subclasses before superclasses).

### How do I verify that my load‑info was processed correctly?

Set the environment variable `MULLE_OBJC_TRACE_LOADINFO=1` before running your program. This enables debug tracing in the loader that prints each `struct _mulle_objc_loadinfo` as it is parsed. For dependency issues, use `MULLE_OBJC_DEBUG_DEPENDENCY=1` to see resolution attempts, or `MULLE_OBJC_WARN_STUCK_LOADABLE=1` to identify classes stranded in wait‑queues due to missing dependencies.