CFHipsterRef Low-Level Programming on iOS & Mac OS X

Dynamically Creating a Class

@interface Product : NSObject
@property (readonly) NSString *name;
@property (readonly) double price;

- (instancetype)initWithName:(NSString *)name price:(double)price;
@end

@implementation Product
- (instancetype)initWithName:(NSString *)name price:(double)price
{
    self = [super init];
    if(!self)
    {
        return nil;
    }

    self.name = name;
    self.price = price;
    return self;
}
@end
  1. First, a class is allocated with objc_allocateClassPair, which specifies the class’s superclass and name.

     Class c = objc_allocateClassPair([NSObject class], "Product", 0);
    
  2. After that, instance variables are added to the class using class_addIvar. at fourth argument is used to determine the variable’s minimum alignment, which depends on the ivar’s type and the target platform architecture.

     class_addIvar(c, "name", sizeof(id), log2(sizeof(id)), @encode(id));
     class_addIvar(c, "price", sizeof(double), sizeof(double), @encode(double));
    
  3. The next step is to define the implementation for the initializer, with imp_implementationWithBlock:. To set name, the call is simply object_setIvar. price is set by performing a memcpy at the previously-calculated offset.

     Ivar nameIvar = class_getInstanceVariable(c, "name");
     ptrdiff_t priceIvarOffset = ivar_getOffset(class_getInstanceVariable(c, "price"));
    
     IMP initIMP = imp_implementationWithBlock( ^(id self, NSString *name, double price)
     {
         object_setIvar(self, nameIvar, name);
    
         char *ptr = ((char *)(__bridge void *)self) + priceIvarOffset;        memcpy(ptr, &price, sizeof(price));
    
         return self;
     });
    
     const char *initTypes = [[NSString stringWithFormat:@"%s%s%s%s%s%s", @encode(id), @encode(id), @encode(SEL), @encode(id), @encode(id), @encode(NSUInteger)] UTF8String];
    
     class_addMethod(c, @selector(initWithFirstName:lastName:age:),
     initIMP, initTypes);
    
  4. Adding methods for the ivar getters follows much the same process.

     IMP nameIMP = imp_implementationWithBlock(^(id self) { return object_getIvar(self, nameIvar);
     });
    
     const char *nameTypes =
     [[NSString stringWithFormat:@"%s%s%s",
     @encode(id), @encode(id), @encode(SEL)] UTF8String];
     class_addMethod(c, @selector(name), nameIMP, nameTypes);
    
     IMP priceIMP = imp_implementationWithBlock(^(id self) {
         char *ptr = ((char *)(__bridge void *)self) + priceIvarOffset;        double price;
    
         memcpy(&price, ptr, sizeof(price));
    
         return price;
     });
    
     const char *priceTypes = [[NSString stringWithFormat:@"%s%s%s", @encode(double), @encode(id), @encode(SEL)] UTF8String];
     class_addMethod(c, @selector(age), priceIMP, priceTypes);
    
  5. Finally, once all of the methods are added, the class is registered with the runtime. And from that point on, Product can be interacted with in Objective-C like any other class:

     objc_registerClassPair(c);
    
     Product *widget = [[Product alloc] initWithName:@"Widget" price:50.00];
     NSLog(@"%@: %g", widget.name, widget.price);