Skip to content

Instantly share code, notes, and snippets.

@jonsterling
Created May 31, 2010 04:57
Show Gist options
  • Save jonsterling/419552 to your computer and use it in GitHub Desktop.
Save jonsterling/419552 to your computer and use it in GitHub Desktop.
JSClass *Dog = [JSClass newClass:@"Dog"
parent:[NSObject class]
properties:[NSArray arrayWithObjects:@"name",@"breed",nil]];
[Dog onClassMessage:@selector(dogWithBreed:)
do:^(id Self, NSString *breed) {
id instance = [[Self alloc] init];
[instance setBreed:breed];
return [instance autorelease];
}];
[Dog addClassProperty:@"kingdom" withValue:@"Animalia"];
[Dog onMessage:@selector(bark) do:^(id _self) { NSLog(@"Yipe!"); }];
id pet = [Dog dogWithBreed:@"Aussie"];
[pet setName:@"Tucker"];
[pet bark];
NSLog(@"pet’s name? %@\n", [pet name]);
NSLog(@"pet’s class? %@; pet’s class’s kingdom? %@\n", [[pet metaclass] name], [[pet metaclass] kingdom]);
NSLog(@"This also works: pet’s class’s kingdom? %@\n", [[pet class] kingdom]);
[Dog setKingdom:@"Metazoa"];
NSLog(@"I’ve changed Dog’s kingdom to %@", [[pet class] kingdom]);
Dog := FSClass newClass:'Dog'.
Dog addProperty:'name'.
Dog addProperty:'breed'.
Dog onClassMessage:#dogWithBreed:
do:[ :self :breed | |instance|
instance := self alloc init.
instance setBreed:breed.
instance
].
Dog addClassProperty:'kingdom' withValue:'Animalia'.
Dog onMessage:#bark do:[ :self | stdout print:'Yipe!' ].
pet := Dog dogWithBreed:'Aussie'.
pet setName:'Tucker'.
pet bark.
// meta-implementations of instance methods with different arg counts
id performMethod0(id self, SEL _cmd);
id performMethod1(id self, SEL _cmd, id arg1);
id performMethod2(id self, SEL _cmd, id arg1, id arg2);
id performMethod3(id self, SEL _cmd, id arg1, id arg2, id arg3);
id performMethod4(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4);
id performMethod5(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5);
id performMethod6(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5, id arg6);
// meta-implementations of class methods with different arg counts
id class_performMethod0(id self, SEL _cmd);
id class_performMethod1(id self, SEL _cmd, id arg1);
id class_performMethod2(id self, SEL _cmd, id arg1, id arg2);
id class_performMethod3(id self, SEL _cmd, id arg1, id arg2, id arg3);
id class_performMethod4(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4);
id class_performMethod5(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5);
id class_performMethod6(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5, id arg6);
id performMethod5(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5) {
JSClass *metaclass = [[self class] metaclass];
JSImpBlock block = [[metaclass _blockForSelector:_cmd] copy];
if (block) {
return block(self, arg1, arg2, arg3, arg4, arg5);
} return nil;
}
id class_performMethod5(id self, SEL _cmd, id arg1, id arg2, id arg3, id arg4, id arg5) {
JSClass *metaclass = [[self class] metaclass];
JSImpBlock block = [[metaclass _class_blockForSelector:_cmd] copy];
if (block) {
return block(self, arg1, arg2, arg3, arg4, arg5);
} return nil;
}
@interface JSClass : NSProxy {
@public
Class _klass;
NSMutableDictionary *_classProperties;
NSMutableDictionary *_selectorMap;
NSMutableDictionary *_classSelectorMap;
}
// initialization
+ initialize;
- initialize;
// help NSProxy determine targets
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- methodSignatureForSelector:(SEL)aSelector;
// class factory
+ newClass:(NSString *)name;
+ newClass:(NSString *)name parent:(Class)superclass;
+ newClass:(NSString *)name parent:(Class)superclass properties:(NSArray *)ivars;
+ classWithExistingClass:(Class)aClass;
// adding methods, protocols, properties
- onMessage:(SEL)selector do:block;
- onClassMessage:(SEL)selector do:block;
- addProtocol:(Protocol *)aProtocol;
- addClassProperty:(NSString *)key withValue:value;
- addClassProperty:(NSString *)key;
// introspection
- (NSString *)name;
@end
static NSMutableDictionary *_classMap = nil;
static BOOL _initialized = NO;
@implementation JSClass
+ initialize {
if (!_initialized) {
_classMap = [NSMutableDictionary new];
_initialized = YES;
} return self;
}
- initialize {
_classProperties = [NSMutableDictionary new];
_selectorMap = [NSMutableDictionary new];
_classSelectorMap = [NSMutableDictionary new];
return self;
}
// ...
#pragma mark -
#pragma mark NSProxy methods
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([_klass respondsToSelector:selector]) {
[anInvocation invokeWithTarget:_klass];
} else {
NSLog(@"%@ does not recognize selector %@", NSStringFromClass(_klass), NSStringFromSelector(selector));
}
}
- methodSignatureForSelector:(SEL)aSelector {
return [_klass methodSignatureForSelector:aSelector];
}
typedef id (^JSImpBlock)(id self, id arg1, ...);
@interface JSClass ()
// class registeration
+ (void)_registerClass:(JSClass *)class forName:(NSString *)name;
+ (JSClass *)_registeredClassWithName:(NSString *)name;
// instance block table registration
- (void)_registerBlock:(JSImpBlock)block forSelector:(SEL)aSelector;
- (JSImpBlock)_blockForSelector:(SEL)aSelector;
// class block table registration
- (void)_class_registerBlock:(JSImpBlock)block forSelector:(SEL)aSelector;
- (JSImpBlock)_class_blockForSelector:(SEL)aSelector;
@end
#pragma mark -
#pragma mark Class/Selector Map Registry
+ (void)_registerClass:(JSClass *)class forName:(NSString *)name {
[_classMap setObject:class forKey:name];
}
+ (JSClass *)_registeredClassWithName:(NSString *)name {
return [_classMap objectForKey:name];
}
- (void)_registerBlock:(JSImpBlock)block forSelector:(SEL)aSelector {
[_selectorMap setObject:[block copy] forKey:[NSValue valueWithPointer:aSelector]];
}
- (JSImpBlock)_blockForSelector:(SEL)aSelector {
return [_selectorMap objectForKey:[NSValue valueWithPointer:aSelector]];
}
- (void)_class_registerBlock:(JSImpBlock)block forSelector:(SEL)aSelector {
[_classSelectorMap setObject:[block copy] forKey:[NSValue valueWithPointer:aSelector]];
}
- (JSImpBlock)_class_blockForSelector:(SEL)aSelector {
return [_classSelectorMap objectForKey:[NSValue valueWithPointer:aSelector]];
}
#pragma mark -
#pragma mark Factory methods
+ newClass:(NSString *)name {
return [self newClass:name parent:[NSObject class]];
}
+ newClass:(NSString *)name parent:(Class)superclass {
return [self newClass:name parent:superclass properties:nil];
}
+ newClass:(NSString *)name parent:(Class)superclass properties:(NSArray *)ivars {
JSClass *metaclass = [[self alloc] initialize];
metaclass->_klass = objc_allocateClassPair(superclass, [name UTF8String], 0);
for (NSString *var in ivars) {
NSString *setter = [NSString stringWithFormat:@"set%@%@:",
[[var substringToIndex:1] uppercaseString],
[var substringFromIndex:1],
nil];
class_addIvar(metaclass->_klass, [var UTF8String], sizeof(id), log2(sizeof(id)), "@");
[metaclass onMessage:NSSelectorFromString(var)
do:^(id _self) {
id property;
object_getInstanceVariable(_self, (const char *)[var UTF8String], (void **)(&property));
return property;
}];
[metaclass onMessage:NSSelectorFromString(setter)
do:^(id _self, id newValue) {
object_setInstanceVariable(_self, (const char *)[var UTF8String], newValue);
}];
}
objc_registerClassPair(metaclass->_klass);
[self _registerClass:metaclass forName:name];
return metaclass;
}
+ classWithExistingClass:(Class)aClass {
NSString *className = NSStringFromClass(aClass);
id registeredClass = [self _registeredClassWithName:className];
if (registeredClass) {
return registeredClass;
}
else {
JSClass *metaclass = [JSClass alloc];
metaclass->_klass = aClass;
return metaclass;
}
}
- onMessage:(SEL)selector do:(id)block {
[self _registerBlock:block forSelector:selector];
NSMutableString *type = [@"@@:" mutableCopy];
NSString *selStr = NSStringFromSelector(selector);
int argCount = 0;
for (int i = 0; i < [selStr length]; ++i) {
if ([selStr characterAtIndex:i] == ':') {
argCount++;
[type appendString:@"@"];
}
}
IMP performImp;
switch (argCount) {
case 0:
performImp = (IMP)performMethod0;
break;
case 1:
performImp = (IMP)performMethod1;
break;
case 2:
performImp = (IMP)performMethod2;
break;
case 3:
performImp = (IMP)performMethod3;
break;
case 4:
performImp = (IMP)performMethod4;
break;
case 5:
performImp = (IMP)performMethod5;
break;
case 6:
performImp = (IMP)performMethod6;
break;
default:
NSLog(@"Too many arguments: JSClass doesn't support more than 6 args per method");
break;
}
class_addMethod(_klass, selector, performImp, [type cStringUsingEncoding:NSASCIIStringEncoding]);
return self;
}
- onClassMessage:(SEL)selector do:(id)block {
[self _class_registerBlock:block forSelector:selector];
NSMutableString *type = [@"@@:" mutableCopy];
NSString *selStr = NSStringFromSelector(selector);
int argCount = 0;
for (int i = 0; i < [selStr length]; ++i) {
if ([selStr characterAtIndex:i] == ':') {
argCount++;
[type appendString:@"@"];
}
}
IMP performImp;
switch (argCount) {
case 0:
performImp = (IMP)class_performMethod0;
break;
case 1:
performImp = (IMP)class_performMethod1;
break;
case 2:
performImp = (IMP)class_performMethod2;
break;
case 3:
performImp = (IMP)class_performMethod3;
break;
case 4:
performImp = (IMP)class_performMethod4;
break;
case 5:
performImp = (IMP)class_performMethod5;
break;
case 6:
performImp = (IMP)class_performMethod6;
break;
default:
break;
}
class_addMethod(objc_getMetaClass(class_getName(_klass)), selector, performImp, [type cStringUsingEncoding:NSASCIIStringEncoding]);
return self;
}
- addProtocol:(Protocol *)aProtocol {
class_addProtocol(_klass, aProtocol);
return self;
}
- addClassProperty:(NSString *)key withValue:value {
[_classProperties setObject:value forKey:key];
NSString *setter = [NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1],
nil];
[self onClassMessage:NSSelectorFromString(key)
do:^(id Self) {
JSClass *metaclass = [object_getClass(Self) metaclass];
return [metaclass->_classProperties objectForKey:key];
}];
[self onClassMessage:NSSelectorFromString(setter)
do:^(id Self, id newValue) {
JSClass *metaclass = [object_getClass(Self) metaclass];
[metaclass->_classProperties setObject:newValue forKey:key];
}];
return self;
}
- addClassProperty:(NSString *)key {
return [self addClassProperty:key withValue:[NSNull null]];
}
- (NSString *)name {
return NSStringFromClass(_klass);
}
@implementation NSObject (JSClass)
+ (JSClass *)metaclass {
return [JSClass classWithExistingClass:self];
}
- (JSClass *)metaclass {
return [[self class] metaclass];
}
@end
2009-11-28 14:46:58.738 Metaclass[5119:a0f] Yipe!
2009-11-28 14:46:58.741 Metaclass[5119:a0f] pet’s name? Tucker
2009-11-28 14:46:58.742 Metaclass[5119:a0f] pet’s class? Dog; pet’s class’s kingdom? Animalia
2009-11-28 14:46:58.743 Metaclass[5119:a0f] This also works: pet’s class’s kingdom? Animalia
2009-11-28 14:46:58.743 Metaclass[5119:a0f] I’ve changed Dog’s kingdom to Metazoa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment