@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
First, a class is allocated with objc_allocateClassPair, which specifies the class’s superclass and name.
Class c = objc_allocateClassPair([NSObject class], "Product", 0);
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));
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);
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);
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);