Created
May 31, 2010 04:57
-
-
Save jonsterling/419552 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation NSObject (JSClass) | |
+ (JSClass *)metaclass { | |
return [JSClass classWithExistingClass:self]; | |
} | |
- (JSClass *)metaclass { | |
return [[self class] metaclass]; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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