Objective-C Runtime lets you create classes entirely at runtime — a capability that powers KVO, Core Data, and many other framework internals that most developers take for granted. The ability to dynamically create classes at runtime is more practical than I’d ever imagined.
Why We Need Dynamic Class Creation
In conventional development, we define classes in .h and .m files, and the compiler determines the class structure at compile time. But some scenarios require class structures that can’t be determined at compile time — for example, dynamically generating models from JSON data, or creating a new class with only specific methods. Additionally, KVO’s underlying implementation dynamically creates a subclass of the target class and overrides property setters to achieve observer notifications.
The core of dynamic class creation is the combination of four functions: objc_allocateClassPair, class_addIvar, class_addMethod, and objc_registerClassPair.
The Complete Flow of Dynamic Class Creation
Step 1: Call objc_allocateClassPair to allocate a class object
This function allocates a class object and returns a Class pointer. Pass in the superclass, class name, and extra memory space size.
Class MyClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
if (!MyClass) {
// Class name already exists or allocation failed
return;
}
If the class name already exists, objc_allocateClassPair returns Nil. So in real projects, you typically use objc_getClass to check if the class already exists first.
Step 2: Add member variables
class_addIvar adds member variables to the class. It must be called before objc_registerClassPair. The reason: a class’s instance memory layout is determined at compile time, and member variables belong to class_ro_t (the read-only part). Once the class is registered with the runtime, the ro is locked and can no longer be modified.
BOOL success = class_addIvar(MyClass, "name", sizeof(NSString *), log2(sizeof(NSString *)), "@");
The fifth parameter is the type encoding, where "@" denotes an object type. log2(sizeof(NSString *)) calculates the alignment — for pointer types, this is 3 (since 8 = 2^3).
Step 3: Add methods
class_addMethod can be called before or after class registration. Unlike member variables, methods are stored in class_rw_t (the read-write part) and can be dynamically added at runtime.
void sayHello(id self, SEL _cmd) {
NSLog(@"Hello from dynamic class!");
}
class_addMethod(MyClass, @selector(sayHello), (IMP)sayHello, "v@:");
The fourth parameter "v@:" is the method type encoding: v means void return, @ means the first parameter is id (self), and : means the second parameter is SEL (_cmd).
Step 4: Register the class
objc_registerClassPair registers the class with the runtime, making it usable. After registration, you can no longer add member variables, but you can continue adding methods and properties.
objc_registerClassPair(MyClass);
Using Dynamically Created Classes and Assigning Properties
Once the class is created, member properties can be assigned in two ways: KVC and object_setIvar.
Method 1: Assignment via KVC
KVC is the most common approach, reading and writing values directly using string keys.
id instance = [[MyClass alloc] init];
[instance setValue:@"John" forKey:@"name"];
NSString *name = [instance valueForKey:@"name"];
NSLog(@"%@", name); // Output: John
Method 2: Direct manipulation via object_setIvar
object_setIvar bypasses KVC’s lookup process and directly operates on member variables, offering better performance. You first need to get the Ivar reference via class_getInstanceVariable.
id instance = [[MyClass alloc] init];
Ivar nameIvar = class_getInstanceVariable(MyClass, "name");
object_setIvar(instance, nameIvar, @"Jane");
NSString *name = object_getIvar(instance, nameIvar);
NSLog(@"%@", name); // Output: Jane
A Complete Dynamic Class Creation Example
#import <objc/runtime.h>
// Dynamic method implementation
void dynamicMethodIMP(id self, SEL _cmd, NSString *param) {
Ivar ivar = class_getInstanceVariable([self class], "name");
id value = object_getIvar(self, ivar);
NSLog(@"name = %@, param = %@", value, param);
}
- (void)createDynamicClass {
// 1. Allocate the class
Class MyClass = objc_allocateClassPair([NSObject class], "Person", 0);
if (!MyClass) return;
// 2. Add member variables
class_addIvar(MyClass, "name", sizeof(NSString *), log2(sizeof(NSString *)), "@");
class_addIvar(MyClass, "age", sizeof(NSInteger), log2(sizeof(NSInteger)), "i");
// 3. Add methods
class_addMethod(MyClass, @selector(print:), (IMP)dynamicMethodIMP, "v@:@");
// 4. Register the class
objc_registerClassPair(MyClass);
// 5. Use it
id person = [[MyClass alloc] init];
[person setValue:@"John" forKey:@"name"];
[person setValue:@25 forKey:@"age"];
// Call the dynamically added method
[person performSelector:@selector(print:) withObject:@"hello"];
}
Important Considerations for Dynamic Class Creation
Member variables must be added before registration: After class registration, class_ro_t is locked — you can’t add more member variables. But methods and properties can still be added after registration since they live in class_rw_t.
Class names must be unique: If the class name already exists, objc_allocateClassPair returns Nil. Use objc_getClass to check first.
Dynamic classes also need memory management: Under ARC, dynamically created classes are also managed by automatic reference counting — instances created with alloc/init are released normally.
I first used dynamic class creation in a project where we needed to generate model classes from a server-side schema at runtime. The server returned a JSON structure describing entity types and their properties, and instead of writing dozens of model classes by hand, we used objc_allocateClassPair and class_addIvar to create them dynamically. Each entity became a fully functional Objective-C class with proper KVC support — and we never had to touch a single .h file.
The biggest gotcha I ran into was adding member variables after registration. I spent an hour debugging why class_addIvar kept returning NO, only to realize I had called objc_registerClassPair too early. The fix is simple: always add all member variables first, then register, then add methods. Think of it as building the foundation before opening the doors.
Summary
The complete flow of dynamic class creation is: objc_allocateClassPair to allocate the class → class_addIvar to add member variables → class_addMethod to add methods → objc_registerClassPair to register the class. Member variables must be added before registration, while methods and properties can be added afterward.
Member properties can be assigned in two ways: KVC for general use, and object_setIvar for better performance in high-frequency scenarios. This mechanism is widely used in KVO, JSON-to-model conversion, and frameworks that require runtime class generation.
A Real Project Scenario: A Schema-Driven User Record
I reproduced this pattern for a plug-in style data importer where a server schema described a small record with name and age fields. The importer needed KVC-compatible objects because an existing reporting layer already consumed records through string keys. A dictionary would have been simpler for storage alone, but it would not have exercised the runtime class and accessor behavior that the integration expected.
The first version only added ivars and property metadata. That was incomplete: class_addProperty records metadata, but it does not synthesize getter and setter implementations. The runnable version explicitly adds the ivars, property attributes, accessors, and a summary method before registering the class.
Building a KVC-Compatible Runtime Property
BOOL ivarAdded = class_addIvar(cls,
"_name",
sizeof(id),
AlignmentExponent(_Alignof(id)),
"@");
objc_property_attribute_t attributes[] = {
{"T", "@"NSString""},
{"&", ""},
{"N", ""},
{"V", "_name"}
};
BOOL propertyAdded = class_addProperty(cls,
"name",
attributes,
4);
BOOL getterAdded = class_addMethod(cls,
@selector(name),
(IMP)GetObjectIvar,
"@@:");
BOOL setterAdded = class_addMethod(cls,
@selector(setName:),
(IMP)SetObjectIvar,
"v@:@");
Every return value is checked. If one step fails, the program disposes of the unregistered class instead of continuing with a partially defined type. Only after all fields and required methods exist does it call objc_registerClassPair.
Corrections and Practical Boundaries
class_addIvarmust run between class allocation and registration. Methods can be added later, but defining the complete class before registration is easier to reason about.- A property entry is metadata. It does not create storage or accessor methods by itself.
- On 64-bit Apple platforms,
NSIntegeris not accurately described by the 32-bitiencoding. The demo uses anNSNumber *object-backed age field so the declared property, ivar storage, and runtime accessors agree. - KVC adds indirection, but Apple documents that its flexibility is useful for Cocoa integrations. Raw ivar access should not be advertised as automatically faster without measuring the real workload.
- For ordinary application models, compile-time classes, dictionaries, or typed decoding are usually easier to maintain. Runtime classes are most appropriate when the type itself truly cannot be known earlier.
The implementation follows Apple’s Objective-C Runtime documentation and the accessor conventions in the Key-Value Coding Programming Guide.
Download and Run the Complete Project
The complete tested source is available in a dedicated repository: Objective-C Runtime Dynamic Record Demo on GitHub.
git clone https://github.com/2252408699/objc-runtime-dynamic-record-demo.git
cd objc-runtime-dynamic-record-demo
make run
Requirements: macOS and Xcode or the Xcode Command Line Tools. The output confirms KVC assignment, runtime getter invocation, the generated summary method, and the registered property attributes.
