Runtime Class Creation

Learn the full runtime class-creation pipeline -- objc_allocateClassPair, class_addIvar, class_addMethod (with correct type-encoding strings), class_addProperty, class_addProtocol, and objc_registerClassPair -- as advanced runtime engineering: how it is possible to synthesize entirely new Objective-C classes at runtime, why a class becomes permanently frozen the moment it is registered, and where this technique is actually used (and where it is a real risk) in production code.

Advanced / Runtime Engineering. Every ordinary Objective-C class in an app is declared in source code and built into the binary's class metadata by the compiler, long before the program ever runs. This lesson covers the other way a class can come to exist: synthesized entirely at runtime, ivar by ivar and method by method, using a small family of C functions the Objective-C runtime exposes for exactly this purpose. It builds directly on the sibling Objective-C Runtime lesson's model of what a class actually is underneath -- a metadata structure the runtime reads, not a compiler-only concept -- and on the sibling Objective-C Type Encodings lesson, whose encoding strings are exactly what class_addMethod requires to install a method correctly. It also sits alongside Method Swizzling and Dynamic Method Resolution as one of three ways application and framework code can reshape runtime behavior beyond what's written in source, though runtime class creation is the most drastic of the three -- it doesn't modify an existing class, it manufactures a brand new one.

Advanced / Runtime Engineering: Why Classes Can Be Built at Runtime at All

The sibling Objective-C Runtime lesson establishes that a class, underneath the language's syntax, is just a metadata structure -- a Class value pointing to data describing a name, a superclass, an ivar layout, a method list, and so on -- that the runtime reads whenever it needs to resolve a message or answer a reflection query like class_getName. Because that structure is just data the runtime reads, and because the runtime itself exposes C functions for constructing that same kind of data, nothing about a class's existence strictly requires it to have been written as an @interface/@implementation pair and compiled in advance.

Runtime class creation is the technique of building exactly that metadata structure programmatically, at runtime, using objc_allocateClassPair to start a new (as-yet-unusable) class, a family of class_add* functions to fill it in piece by piece, and objc_registerClassPair to make it real and usable. This is squarely advanced, low-level runtime engineering -- it operates directly on the same C metadata structures the compiler would otherwise generate for you, with none of the compiler's own safety checks, and it is not a technique ordinary application code reaches for casually.

The diagram accompanying this lesson lays out the exact pipeline this lesson walks through step by step, in the fixed order these operations must happen in: allocate the class, add its ivars, add its methods, add its properties, add its protocols, and only then register it -- after which, as the later section on limitations covers in detail, its shape is permanently frozen.

• A class is, underneath the syntax, a metadata structure the runtime reads -- this lesson builds that structure directly with C functions instead of via @interface/@implementation. • This is Advanced / Runtime Engineering: it operates on raw runtime metadata with none of the compiler's usual safety checks. • The pipeline has a fixed order: allocate, add ivars, add methods, add properties, add protocols, then register. • It sits alongside Method Swizzling and Dynamic Method Resolution as ways of reshaping runtime behavior -- but this technique manufactures an entirely new class rather than modifying an existing one.

Stage One: objc_allocateClassPair

objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) is the pipeline's first step: it allocates a brand-new, as-yet-unregistered class and its associated metaclass (the 'pair' in the function's name) as a subclass of superclass, under the given name. extraBytes is almost always passed as 0 in ordinary use -- it reserves extra space in the class and metaclass structures themselves for specialized, rarely-needed indexed ivars, distinct from the ordinary per-instance ivars added in the next stage.

The class this function returns is real, in the sense that it is a genuine Class value with a genuine name and superclass link, but it is not yet usable for creating instances or receiving messages -- it exists in a construction-only state, and every remaining stage of the pipeline (adding ivars, methods, properties, and protocols) must happen before objc_registerClassPair makes it live. Calling alloc/init on it, or sending it any ordinary message, before registration is undefined and not supported.

A failure case worth knowing: objc_allocateClassPair returns Nil if a class with the given name already exists anywhere the runtime knows about (including classes from linked frameworks), so production code that generates classes dynamically -- the KVO and mocking-framework use cases covered later in this lesson -- typically generates a unique name, often by appending an identifier to a base name, to guarantee this call succeeds.

• objc_allocateClassPair(superclass, name, extraBytes) allocates a new class and its metaclass, unregistered and not yet usable. • extraBytes is almost always 0 in ordinary use; it reserves space for rarely-needed indexed ivars, not ordinary per-instance ivars. • The returned class cannot be instantiated or messaged normally until every remaining pipeline stage runs and objc_registerClassPair is called. • Returns Nil if a class with that name already exists -- dynamic-class-generating code typically ensures a unique name.

Stage Two: class_addIvar — Adding Instance Variables

class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types) adds a new instance variable to a class that has been allocated but not yet registered. Unlike methods, which can be added to an already-registered class (see the caching caveats in the sibling Dynamic Method Resolution and Method Swizzling lessons), ivars can ONLY ever be added before registration -- this is the single most important limitation covered later in this lesson, because an instance's memory layout, once a class is registered and instances of it may exist, can never be resized again.

size and alignment describe the exact memory footprint the new ivar needs, and types is the same kind of type-encoding string covered in full in the sibling Objective-C Type Encodings lesson -- for a simple object ivar, size is typically sizeof(id), alignment is log2(sizeof(id)) via a helper like the standard idiom shown in the example below, and types is "@".

Because ivar layout must be completely finalized before objc_registerClassPair runs, every call to class_addIvar for a given class must happen between that class's objc_allocateClassPair call and its objc_registerClassPair call -- there is no way to add storage to instances of an already-live, registered class through this mechanism, which is exactly why associated objects (covered in their own sibling lesson) exist as a separate technique for attaching storage to already-registered classes and categories.

• class_addIvar(cls, name, size, alignment, types) adds a new instance variable -- but only to a class not yet registered. • Ivars can NEVER be added after objc_registerClassPair runs; this is the pipeline's hardest ordering constraint. • size, alignment, and types describe the ivar's exact memory layout, using the same type-encoding alphabet as method signatures. • This is exactly why associated objects exist as a separate mechanism -- for attaching storage to classes that are already registered, such as via a category.

Stage Three: class_addMethod — Adding Methods With Correct Type Encodings

class_addMethod(Class cls, SEL name, IMP imp, const char *types) installs a method implementation for a given selector on a class. Unlike ivars, class_addMethod can technically be called on an already-registered class too (this is the exact mechanism the sibling Dynamic Method Resolution lesson's resolveInstanceMethod: uses), but in the runtime-class-creation pipeline it is used during construction, before registration, to give the new class its behavior.

The types parameter is, once again, a full method type-encoding string exactly as described in the sibling Objective-C Type Encodings lesson -- return type, total argument-frame size, and then self ('@'), _cmd (':'), and each declared parameter with its byte offset. Getting this string wrong is not a cosmetic mistake: it is the same class of memory-corruption risk that lesson describes for NSInvocation, because the runtime trusts this string completely when later messages are sent to instances of the new class.

imp is a raw C function pointer whose actual parameter list must match a conventional Objective-C method implementation's shape -- its first two parameters are always the receiver (id) and the selector (SEL), exactly the self/_cmd pair the type-encoding string also describes, followed by any declared arguments -- and it is cast to IMP (a typedef for a generic function pointer type) when passed to class_addMethod.

• class_addMethod(cls, selector, imp, types) installs an implementation for a selector; used during construction to give the new class behavior. • types must be a complete, correct method type-encoding string, exactly as covered in the Objective-C Type Encodings lesson. • A wrong types string is a genuine memory-corruption risk, not a cosmetic error, once the class is used. • imp's real parameter list always begins with (id self, SEL _cmd), matching the encoding string's self/_cmd pair, before any declared arguments.

Stage Four: class_addProperty and class_addProtocol

class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) adds a property to the class's metadata -- but it is important to understand exactly what this does and does not do: it registers the property's metadata (its name, attributes like readonly/nonatomic/copy, and its associated type encoding) so that reflection APIs like class_copyPropertyList and Key-Value Coding can see and describe it, but it does NOT automatically generate a backing ivar or accessor methods the way @property does at compile time. Those still have to be supplied separately -- the ivar via class_addIvar in stage two, and the getter/setter via class_addMethod in stage three -- for the property to actually behave like an ordinary Objective-C property at runtime.

class_addProtocol(Class cls, Protocol *protocol) declares that the class formally conforms to a given protocol, which is again purely a metadata declaration -- it makes conformsToProtocol: return YES and makes the protocol's methods show up in reflection, but it does not verify, and does not require, that the class actually implements every method the protocol declares. As with ordinary compiled code, a class can technically declare conformance to a protocol without implementing all of its required methods; the runtime does not enforce that the way the compiler's static analysis would for an @interface written directly in source.

Both of these calls, like class_addIvar, are typically made before objc_registerClassPair in the construction pipeline, though unlike ivars, properties and protocol conformance can in practice also be added to an already-registered class -- it is simply conventional, and clearer, to do all of a runtime-generated class's setup in one place before registering it.

• class_addProperty adds property METADATA only -- it does not generate a backing ivar or accessor methods; those need class_addIvar and class_addMethod separately. • class_addProtocol declares formal protocol conformance as metadata -- it does not verify or require that every required method is actually implemented. • Both are conventionally done before registration alongside ivars and methods, for a single, clear construction pipeline. • Reflection APIs (class_copyPropertyList, conformsToProtocol:) read exactly this metadata back out later.

Stage Five: objc_registerClassPair — Making the Class Live

objc_registerClassPair(Class cls) is the final stage: it takes the fully-constructed-but-still-inert class produced by every earlier stage and registers it with the runtime, at which point it becomes a genuine, fully usable Objective-C class -- instances can be allocated with +alloc, it appears in class lists, and ordinary message sends to it and its instances go through the exact same dispatch pipeline (cache, method lookup, and so on) covered in the objc_msgSend in Depth lesson, indistinguishable at the messaging level from a class the compiler generated from source.

This call must happen exactly once per class pair, and only after every ivar, method, property, and protocol the class needs has already been added -- the section immediately below covers, in detail, why nothing can be added to the ivar layout after this point.

Once this call returns successfully, the new Class value is a completely ordinary citizen of the runtime from the caller's perspective: it can be passed to NSStringFromClass, looked up later by name with NSClassFromString, subclassed further (including via another full runtime-class-creation pipeline, or via an ordinary compiled @interface), and instances of it participate in ARC, KVO, and every other ordinary Objective-C object-lifecycle mechanism exactly like any other class.

• objc_registerClassPair finalizes construction and makes the class genuinely live -- instances can now be allocated and messaged normally. • It must be called exactly once, after every ivar/method/property/protocol addition is already complete. • After registration, the class is indistinguishable at the messaging level from one generated by the compiler from source. • Instances of it participate fully in ARC, KVO, and every other ordinary Objective-C lifecycle mechanism.

Why a Class Cannot Be Modified After Registration

The single hardest constraint in this whole pipeline is that class_addIvar unconditionally fails once objc_registerClassPair has been called -- there is no supported way to add storage to instances of an already-registered class through this mechanism, ever, for the lifetime of the process. The reason is structural, not an arbitrary API restriction: once a class is registered, instances of it (or of any of its subclasses) may already exist, or may come to exist at any moment thereafter, each with a memory layout computed from the ivar list as it stood at registration time. Growing that layout retroactively would require finding and resizing every already-allocated instance in memory, which the runtime has no general way to do and does not attempt.

This is exactly why associated objects, covered in their own sibling lesson, exist as a wholly separate mechanism: they let code attach additional storage to instances of an already-registered class (most visibly through categories, which cannot add ivars at all, compiled or otherwise) by keeping that storage in a side table keyed by object identity, rather than by growing the object's own in-memory layout.

Methods, properties, and protocol conformance are comparatively more flexible after registration -- class_addMethod, class_addProperty, and class_addProtocol can technically still be called successfully afterward, which is precisely the mechanism Dynamic Method Resolution relies on -- but ivars are the one piece of a class's shape that is permanently frozen the moment objc_registerClassPair returns.

• class_addIvar fails, permanently, on any class that has already been registered -- there is no supported workaround. • The reason is structural: existing instances' memory layout is already fixed, and the runtime cannot retroactively resize live objects. • This is exactly why associated objects exist as a separate side-table mechanism for adding storage after registration. • Methods, properties, and protocol conformance remain addable after registration -- only ivars are permanently frozen.

Real-World Risks and Where This Technique Is Actually Used

Runtime class creation is rarely, if ever, something ordinary application code should reach for directly -- when application code wants dynamic-feeling behavior, dynamic method resolution or associated objects on an existing class almost always solve the problem with far less risk and complexity. The technique's real home is framework and tooling code: Apple's own Key-Value Observing implementation famously creates a private runtime-generated subclass the first time an object is observed, isa-swizzling the observed instance to point at that dynamically-created subclass so it can override the observed property's setter -- a use case that connects directly to the sibling Method Swizzling lesson's coverage of isa-swizzling. Object-mocking and dynamic-proxy libraries in testing frameworks use the same technique to synthesize a class on the fly that conforms to whatever protocol a test needs to mock, without that class having existed in source at all.

The risks are real and specific. Thread safety is one: objc_allocateClassPair and objc_registerClassPair are not safe to race against each other or against ordinary class lookups for the same name from multiple threads without external synchronization, since they mutate shared, process-wide runtime metadata tables. Name collisions are another: because class names are process-wide and reused across every linked framework, generating classes with predictable or colliding names risks the Nil-returning failure covered in stage one, or worse, accidentally colliding with a framework-internal class name. And because every registered class lives for the life of the process -- there is no supported way to unregister or deallocate a class pair once registered -- generating classes in a loop or on every call to some hot code path is a permanent, unbounded memory leak of runtime metadata, not a reclaimable resource.

In short: this is a real, supported, and occasionally necessary technique, but it is squarely advanced runtime engineering meant for a small number of well-understood framework-level use cases, not a general-purpose tool for everyday application logic.

• Real-world use is mostly framework-level: KVO's private runtime-generated subclasses (via isa-swizzling, see Method Swizzling) and mocking/dynamic-proxy libraries in test frameworks. • Thread safety: allocating/registering classes mutates shared, process-wide runtime metadata and needs external synchronization against concurrent use. • Name collisions: class names are process-wide across every linked framework; unplanned or colliding names can fail or clash. • Registered classes are never unregistered for the life of the process -- generating them repeatedly is an unbounded, permanent metadata leak, not a reclaimable resource.

Code Examples

This walks through the full diagram end to end: allocate the class pair, add its one ivar (an object pointer, encoded '@'), add its one method with a correct type-encoding string ('v@:' -- void return, self, _cmd, matching the Objective-C Type Encodings lesson's rules exactly), then register it. Only after objc_registerClassPair returns is the class usable for +alloc and ordinary messaging.

class_addMethod(newClass, @selector(greet), (IMP)greetIMP, "v@:");

class_addIvar unconditionally returns NO once a class has been registered, regardless of whether any instances actually exist yet -- the constraint is about the class's finalized layout, not about live instances specifically. This is the concrete demonstration of the limitation covered in the 'Why a Class Cannot Be Modified After Registration' section above.

BOOL added = class_addIvar(newClass, "_tooLate", sizeof(id), (uint8_t)log2(sizeof(id)), "@"); // NO

Common mistake: Assuming class_addIvar can be called at any time the way class_addMethod technically can -- ivars are the one piece of a runtime-generated class's shape that must be completely finalized before objc_registerClassPair, with no exceptions.

class_addProtocol makes the class report conformance via conformsToProtocol: but does not check that -greet is actually implemented -- that's still the caller's responsibility, via class_addMethod. class_addProperty similarly only records the property's name and attributes for reflection (class_copyPropertyList, KVC) -- it does not synthesize a backing ivar or accessor methods the way @property does at compile time; those must still be added separately.

class_addProperty(newClass, "storedName", attrs, 2); // metadata only

This is the canonical real-world use of runtime class creation: Apple's own KVO implementation synthesizes a private subclass the first time an object is observed, overrides the relevant setters to inject change notifications, registers the subclass, and then isa-swizzles the single observed instance (not the whole class) to point at it -- the isa-swizzling step is covered in full in the sibling Method Swizzling lesson. Ordinary application code should recognize this pattern rather than reimplement it.

object_setClass(observedPersonInstance, kvoClass);

// Step 1: Allocate Class -- a new subclass of NSObject named "DynamicGreeter".
Class newClass = objc_allocateClassPair([NSObject class], "DynamicGreeter", 0);
if (newClass == Nil) {
    NSLog(@"A class named DynamicGreeter already exists!");
}

// Step 2: Add Ivars -- one object ivar to hold a name string.
class_addIvar(newClass, "_storedName", sizeof(id), (uint8_t)log2(sizeof(id)), "@");

// Step 3: Add Methods -- a -greet method, with a correct type-encoding string.
void greetIMP(id self, SEL _cmd) {
    id name = object_getIvar(self, class_getInstanceVariable([self class], "_storedName"));
    NSLog(@"Hello from a runtime-generated class, %@!", name);
}
class_addMethod(newClass, @selector(greet), (IMP)greetIMP, "v@:");

// (Add Properties / Add Protocols would go here if needed -- omitted for brevity.)

// Step 6: Register Class -- the class becomes live and usable.
objc_registerClassPair(newClass);

// Step 7: Runtime Class Available -- use it exactly like any compiled class.
id instance = [[newClass alloc] init];
object_setIvar(instance, class_getInstanceVariable(newClass, "_storedName"), @"World");
[instance performSelector:@selector(greet)];   // "Hello from a runtime-gener
…
objc_registerClassPair(newClass);   // class is now live

BOOL added = class_addIvar(newClass, "_tooLate", sizeof(id),
                            (uint8_t)log2(sizeof(id)), "@");
NSLog(@"%d", added);   // 0 (NO) -- always fails after registration

// The class's ivar layout is permanently frozen the moment
// objc_registerClassPair returned -- there is no supported way
// around this, for the lifetime of the process.
@protocol Greetable <NSObject>
- (void)greet;
@end

// Declare conformance -- metadata only, does not verify -greet exists yet.
class_addProtocol(newClass, @protocol(Greetable));

objc_property_attribute_t typeAttr  = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" };   // copy
objc_property_attribute_t attrs[] = { typeAttr, ownership };
class_addProperty(newClass, "storedName", attrs, 2);

// class_addProperty registered METADATA ONLY -- no backing ivar and no
// accessor methods were generated. Both still need class_addIvar and
// class_addMethod, exactly as in the previous example, for
// -storedName / -setStoredName: to actually work.
// Conceptual illustration of what -addObserver:forKeyPath:... does
// internally the first time an object is observed -- NOT code you
// would write yourself; shown to connect this lesson to Method Swizzling.

// 1. Allocate a new class pair as a subclass of the observed object's
//    real class, e.g. "NSKVONotifying_Person" for a Person instance.
Class kvoClass = objc_allocateClassPair([Person class], "NSKVONotifying_Person", 0);

// 2. Add overriding setter methods (e.g. -setName:) that call through
//    to the original implementation but also fire KVO notifications.
class_addMethod(kvoClass, @selector(setName:), (IMP)kvo_setName, "v@:@");

// 3. Register the new subclass.
objc_registerClassPair(kvoClass);

// 4. isa-swizzle the OBSERVED INSTANCE (not the class) to point at
//    this new subclass -- covered in full in the Method Swizzling lesson.
object_setClass(observedPersonInstance, kvoClass);

Runtime Class Creation Pipeline (Advanced / Runtime Engineering)

The fixed construction order for a runtime-generated class -- everything below 'Register Class' can only happen while the class is still in its inert, pre-registration state; ivars in particular can never be added after this pipeline reaches 'Register Class'.

Allocate Class
     ↓
Add Ivars
     ↓
Add Methods
     ↓
Add Properties
     ↓
Add Protocols
     ↓
Register Class
     ↓
Runtime Class Available

Frameworks, Not Application Code

Outside of Key-Value Observing's internal implementation and object-mocking/dynamic-proxy libraries used in testing, runtime class creation almost never shows up in ordinary application code, and that's the correct default: Dynamic Method Resolution and associated objects solve the vast majority of 'I need dynamic-feeling behavior' problems with far less risk, no thread-safety burden around shared class-metadata tables, and no permanent per-class memory cost for the life of the process. Reach for this pipeline only when the actual requirement is synthesizing a genuinely new class shape at runtime -- not merely adding storage or behavior to an existing one.

What state is a class in immediately after objc_allocateClassPair returns, before any other pipeline stage runs? A. Fully usable -- instances can already be allocated with +alloc B. Allocated but inert -- it has a name and superclass link but cannot yet be used to create instances or receive messages C. Registered, but with no ivars or methods yet D. It does not exist until objc_registerClassPair is called

B. Allocated but inert -- it has a name and superclass link but cannot yet be used to create instances or receive messages — objc_allocateClassPair produces a real Class value with a name and superclass, but it remains in a construction-only, unregistered state. Every remaining stage -- adding ivars, methods, properties, and protocols -- must happen before objc_registerClassPair makes the class genuinely usable.

Why can class_addIvar never succeed on a class after objc_registerClassPair has already been called on it? A. It's an arbitrary API restriction that could technically be worked around with private APIs B. Because instances of the class may already exist (or come to exist) with a memory layout fixed at registration time, and the runtime has no way to retroactively resize already-allocated instances C. Because ivars can only be added to root classes like NSObject, never to subclasses D. Because class_addIvar is only valid inside a category, not on a runtime-allocated class

B. Because instances of the class may already exist (or come to exist) with a memory layout fixed at registration time, and the runtime has no way to retroactively resize already-allocated instances — The constraint is structural, not arbitrary: once registered, instances may exist with a layout computed from the ivar list at registration time, and growing that layout retroactively would require resizing every already-allocated instance in memory, which the runtime cannot do.

What does class_addProperty actually do, and what does it NOT do? A. It generates a full property with a backing ivar and synthesized accessor methods, exactly like @property does at compile time B. It only registers property metadata (name, attributes) for reflection -- it does not create a backing ivar or accessor methods, which must be added separately C. It is only usable after objc_registerClassPair, never before D. It automatically infers and adds the required ivar via class_addIvar internally

B. It only registers property metadata (name, attributes) for reflection -- it does not create a backing ivar or accessor methods, which must be added separately — class_addProperty registers metadata only, visible to reflection APIs like class_copyPropertyList and to Key-Value Coding. A working property still needs its ivar added via class_addIvar and its accessor methods added via class_addMethod separately.

In the correct pipeline order, which stage must ALWAYS come last? A. class_addMethod B. class_addProtocol C. objc_registerClassPair D. class_addProperty

C. objc_registerClassPair — objc_registerClassPair finalizes and activates the class, and must run only after every ivar, method, property, and protocol the class needs has already been added -- it is the last stage in the diagram's pipeline, after which the class becomes genuinely usable.

Which real-world Apple framework feature is described in this lesson as using runtime class creation internally, connecting to the sibling Method Swizzling lesson? A. Auto Layout's constraint solver B. Key-Value Observing (KVO), which creates a private runtime-generated subclass and isa-swizzles observed instances to point at it C. Grand Central Dispatch's queue implementation D. Core Data's managed object context

B. Key-Value Observing (KVO), which creates a private runtime-generated subclass and isa-swizzles observed instances to point at it — KVO synthesizes a private subclass the first time an object is observed, overrides the relevant setters to inject change notifications, registers the subclass with objc_registerClassPair, and then isa-swizzles the specific observed instance (not the whole class) to point at it -- the isa-swizzling mechanism is covered fully in the Method Swizzling lesson.

Why is generating runtime classes repeatedly (for example, inside a hot loop or on every call to some function) considered a real risk rather than just inefficient? A. Because objc_allocateClassPair is extremely CPU-intensive, causing noticeable slowdowns only B. Because there is no supported way to unregister or deallocate a registered class pair -- each one persists for the life of the process, so repeated generation is an unbounded, permanent metadata leak C. Because each new class automatically spawns a new thread D. Because the compiler rejects programs that call objc_allocateClassPair more than once

B. Because there is no supported way to unregister or deallocate a registered class pair -- each one persists for the life of the process, so repeated generation is an unbounded, permanent metadata leak — Registered classes are never unregistered or freed for the lifetime of the process. Generating them repeatedly therefore leaks runtime metadata permanently and without bound, which is a correctness/resource risk, not merely a performance concern.

Write the full runtime-class-creation pipeline for a class named 'DynamicCounter', a direct subclass of NSObject, with one int ivar named '_count' and one method '- (void)increment' that increments and logs it. Then explain, referencing the pipeline's ordering constraint, why a second ivar '_step' could NOT be added after you call objc_registerClassPair, and name the sibling mechanism that would let you attach '_step'-like storage to an already-registered class instead.

Class cls = objc_allocateClassPair([NSObject class], "DynamicCounter", 0);\nclass_addIvar(cls, "_count", sizeof(int), (uint8_t)log2(sizeof(int)), "i");\nclass_addMethod(cls, @selector(increment), (IMP)incrementIMP, "v@:");\nobjc_registerClassPair(cls);\n\n'_step' could not be added afterward because class_addIvar unconditionally fails once a class is registered -- the class's ivar layout, and therefore the memory footprint of every instance that may already exist, is permanently frozen the moment objc_registerClassPair returns; there is no supported way to resize already-allocated instances. The sibling mechanism for attaching storage to an already-registered class is associated objects, covered in the Associated Objects and Associated Object Policies lessons, which keep such storage in a side table keyed by object identity instead of growing the object's own layout.

An int ivar's encoding is "i"; its size is sizeof(int) and alignment is log2(sizeof(int)). The increment method's type-encoding string is "v@:" (void return, self, _cmd, no declared arguments). For the second part, think about what mechanism this lesson names as the alternative to class_addIvar for already-registered classes.