How to Load Classes, Categories, and Methods at Runtime with Mulle‑ObjC‑Runtime
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 and 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 (
classestoloadandcategoriestoload) 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+loadmethods 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). As detailed in src/mulle-objc-load.h, these structures contain:
struct _mulle_objc_loadclasslist *classlist– array of class descriptorsstruct _mulle_objc_loadcategorylist *categorylist– array of category descriptorsversion.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 processes enqueued load-info through six distinct phases:
- Parse load‑info – Iterate over
classlistandcategorylistarrays - Resolve dependencies – Check superclasses via
_mulle_objc_universe_lookup_infraclass(); store missing dependencies in wait‑queues using_mulle_objc_map_append_info() - Queue
+loadmethods – Add class and category+loadimplementations to theloadscall‑queue viamulle_objc_callqueue_add() - Satisfy wait‑queues – Re-examine pending entries after each registration; process dependencies once unblocked
- Execute
+loadqueue – Run accumulated+loadcalls in specification order (superclass before subclass, class before category) - Mark universe bits – Set flags like
MULLE_OBJC_UNIVERSE_HAVE_TPS_CLASSESafter 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) 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:
#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:
/* 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:
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:
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 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:
#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()fromsrc/mulle-objc-load.h - Dependency resolution occurs automatically through the universe's wait‑queues (
classestoloadandcategoriestoload) when superclasses or required categories are initially missing +loadexecution order is maintained through a dedicated call‑queue, ensuring superclass+loadruns before subclass+load, and class+loadruns 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 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 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.
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 →