Skip to content

Instantly share code, notes, and snippets.

@jonsterling
Created August 20, 2010 06:37
Show Gist options
  • Save jonsterling/539747 to your computer and use it in GitHub Desktop.
Save jonsterling/539747 to your computer and use it in GitHub Desktop.
Objectify
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <objc/message.h>
#import <string>
#import <map>
#import <typeinfo>
#define $(value) box<typeof(value)>(value)
using namespace std;
template <typename T> id box(T v) {
typedef pair<Class,SEL> m;
map<const char *,m> ops;
ops[@encode(int)] = m([NSNumber class], @selector(numberWithInt:));
ops[@encode(float)] = m([NSNumber class], @selector(numberWithFloat:));
ops[@encode(double)] = m([NSNumber class], @selector(numberWithDouble:));
ops[@encode(BOOL)] = m([NSNumber class], @selector(numberWithBool:));
ops[@encode(const char *)] = m([NSString class], @selector(stringWithUTF8String:));
ops[@encode(SEL)] = m([NSString class], @selector(objectify_stringFromSelector:));
ops[@encode(Class)] = m([NSString class], @selector(objectify_stringFromClass:));
ops[@encode(CGPoint)] = m([NSValue class], @selector(valueWithCGPoint:));
ops[@encode(CGRect)] = m([NSValue class], @selector(valueWithCGRect:));
ops[@encode(CGSize)] = m([NSValue class], @selector(valueWithCGSize:));
ops[@encode(NSRange)] = m([NSString class], @selector(objectify_stringFromRange:));
ops[@encode(void *)] = m([NSValue class], @selector(valueWithPointer:));
m method = ops[@encode(T)];
Class klass = method.first ? method.first : [NSObject class];
SEL s = method.second ? method.second : @selector(objectify_identity:);
id result = objc_msgSend(klass, s, v);
return result;
}
template <typename T> T unbox(id v) {
map<const char *,SEL> ops;
ops[@encode(int)] = @selector(intValue);
ops[@encode(float)] = @selector(floatValue);
ops[@encode(double)] = @selector(doubleValue);
ops[@encode(BOOL)] = @selector(boolValue);
ops[@encode(const char *)] = @selector(UTF8String);
ops[@encode(SEL)] = @selector(objectify_selectorValue);
ops[@encode(Class)] = @selector(objectify_classValue);
ops[@encode(CGPoint)] = @selector(CGPointValue);
ops[@encode(CGRect)] = @selector(CGRectValue);
ops[@encode(CGSize)] = @selector(CGSizeValue);
ops[@encode(NSRange)] = @selector(objectify_rangeValue);
ops[@encode(void *)] = @selector(pointerValue);
SEL method = ops[@encode(T)] ? ops[@encode(T)] : @selector(objectify_identity);
typedef T (*SendFunctionType)(id, SEL);
return ((SendFunctionType)objc_msgSend)(v, method);
}
#import "Objectify.h"
@implementation NSString (Objectify)
+ (id)objectify_stringFromSelector:(SEL)aSelector {
return NSStringFromSelector(aSelector);
}
- (SEL)objectify_selectorValue {
return NSSelectorFromString(self);
}
+ (id)objectify_stringFromClass:(Class)aClass {
return NSStringFromClass(aClass);
}
- (Class)objectify_classValue {
return NSClassFromString(self);
}
+ (id)objectify_stringFromRange:(NSRange)aRange{
return NSStringFromRange(aRange);
}
- (NSRange)objectify_rangeValue {
return NSRangeFromString(self);
}
@end
@implementation NSObject (Objectify)
+ (id)objectify_identity:(id)object { return object; }
- (id)objectify_identity { return self; }
@end
#import <Foundation/Foundation.h>
#import "Objectify.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
const char *str = "wut";
id o = $<const char *>(str);
NSLog(@"char: %s, string: %@, of class: %@", str, o, [o class]);
// => char: wut, string: wut, of class: NSCFString
[pool drain];
}
@jonsterling
Copy link
Author

Yes, well said with respect to inference. As for autoboxing, I think you are right to a point. Haskell, at it's lowest implementation levels, does indeed involve autoboxing of primitives, as does Smalltalk; yet at the programmer's level, these are always values and objects respectively. From my extremely limited understanding, this sort of boxing is a performance measure, but I may be misunderstanding something.

(My Haskell bias demands I should point out the benefits of handling as much type-checking at compile-time as possible)

@jtbandes
Copy link

I can certainly believe it's a performance measure, since a main reason to use primitives over boxed values is for performance. Much as it's not collectable and annoying, int is faster than NSNumber ;)

@jtbandes
Copy link

Type checking at compile time seems like the best option, but in order to check, for example, a template enough to be sure it will work would involve traversing through the steps and types each place that template is used in the code. I would think that'd be a very resource-intensive operation. And it would also not allow such haxxery as truly dynamic typing — that is, if a type were read as input to the program and used (à la NSClassFromString or Ruby's const_get).

@jonsterling
Copy link
Author

True. When working with a good static type system, the way you reason about solutions changes; problems that were solved previously using metaprogramming can be solved just as succinctly elsewise. But metaprogramming is so damned fun…

@jonsterling
Copy link
Author

As for resource-intensive checking, just try compiling any Haskell program. Seems to work quickly enough for me… :)

@haikusw
Copy link

haikusw commented Aug 20, 2010

not really following along completely, but are you calling the objc version that gave warnings like so?

id to_object( void * x )
{
    return [NSNumber numberWithInt: (int)x];
}

int a = 42;
id foo = to_object( a );

???

If you do it this way it doesn't give a warning:

id to_object( void * x )
{
    return [NSNumber numberWithInt: *(int*)x];
}

int a = 42;
id foo = to_object( &a );

requires sending in the address of the thing you want converted though...

@jonsterling
Copy link
Author

Wow, Tyler, you're totally right. Should have thought of that. :)

@haikusw
Copy link

haikusw commented Aug 20, 2010

maybe it prevents detecting the type though... didn't try to actually do what you were trying to do so not sure. Another other option would be to use C's var-args as the parameter because you can get their types moderately easily. I'm almost motivated enough to have a non-C++ version to sit down and write it, but a little pressed for time at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment