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];
}
@jtbandes
Copy link

What warnings? Plenty of Cocoa things take a void * context.

@jonsterling
Copy link
Author

Two problems. The first:

id lame(void *l) {
  //...
  return [NSNumber numberWithInt:(int)l]; // ERROR: cast from 'void *' to 'int' loses precision
}

This first issue can be gotten around by using objc_msgSend, though. Not a big deal.

The second problem is that @encode will always give us "^v", because it is not smart enough to realize that we did an unsafe cast. It's just going to use whatever it was given.

@jtbandes
Copy link

Yes, you'll have to use something like #define VAL(o) (lame(@encode(typeof(o)), (o))) with id lame(char *type, void *l);.

I get the strange feeling of writing Lisp... :)

@jonsterling
Copy link
Author

Yeah. In fact, originally I was doing something almost identical to that. But then I decided the C++ route was more elegant…

@jtbandes
Copy link

C++, elegant? lolz
Actually, yeah, it kind of is.

@jonsterling
Copy link
Author

C++ is a little dirty, but you know what? Any day, I'll take a dictionary that lets me put anything I want into it (under the condition that all my values and keys be unified in type). Any day, I'll take a dictionary that lets me access it using a custom operator instead of ridiculously long and bureaucratic messages.

The purity of Smalltalk-messaging is cool and all, but the Smalltalk paradigm is only really useful, I think, in a language that has autoboxing. Like Smalltalk (or Ruby). Also, the Smalltalk libraries had simpler method names. For instance, dict at:#sym put:"dawg" instead of dict setValue:@"dawg" forKey:kSym". I still prefer dict[:sym] = "dawg"

Also, templates are cool. They'd be less cumbersome with actual, behind-the-scenes type inference, but it's still pretty freaking cool.

@jonsterling
Copy link
Author

heheh

@jtbandes
Copy link

Ruby doesn't have autoboxing per se, but boxing by virtue of the massively object-orientedness of it. If everything is an object, no boxing is ever needed. And that's why it's awesome.

@jonsterling
Copy link
Author

Indeed, thanks for the correction.

@jtbandes
Copy link

In short: Type safety is cool, but also sucks.

@jonsterling
Copy link
Author

Humbug. I think the reason people get annoyed with type safety is that they think it means a lot of extra work. And then they come out with solutions that produce the right result most of the time, but are by their very nature incorrect. Type-safety means that we won't be tricked into thinking things work.

But type-safety in a language lacking quality type inference IS A BITCH. Really.

@jtbandes
Copy link

It seems inference really is the key, yes, so that you can pass values around and store them in variables whose types are determined at compile time or even runtime. The programmer is not concerned with the types of the data, merely with the data itself and its manipulation; and it's the compiler's/runtime's job to check that the types are being properly combined.

That said, the need for autoboxing is an indication of a limited type system. For example, C++ doesn't need autoboxing because of templates.

(btw: please inform me if I have no idea what I'm talking about. Apart from it being late, I haven't been stretching myself programming-wise lately; just using Obj-C at work, and other technologies I'm familiar with.)

@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