Skip to content

Instantly share code, notes, and snippets.

@fjolnir
Created July 11, 2012 08:14
Show Gist options
  • Save fjolnir/3088888 to your computer and use it in GitHub Desktop.
Save fjolnir/3088888 to your computer and use it in GitHub Desktop.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
#define BOXER_PREFIX "Boxer_"
#define BlockImp imp_implementationWithBlock
@interface Boxer : NSObject {
@protected
void *_ptr; // Points to the boxed value
NSUInteger _size;
BOOL _isOnHeap;
}
@property(readonly) void *valuePtr;
+ (Boxer *)box:(void *)aPtr;
+ (Boxer *)box:(void *)aPtr withType:(const char *)aType;
+ (Boxer *)box:(void *)aPtr withType:(const char *)aType copy:(BOOL)aShouldCopy;
+ (void)unbox:(id)aValue to:(void *)aDest usingType:(const char *)aType;
+ (BOOL)typeIsScalar:(const char *)aType;
@end
@implementation Boxer
@synthesize valuePtr=_ptr;
+ (id)box:(void *)aPtr withType:(const char *)aType
{
return [self box:aPtr withType:aType copy:NO];
}
+ (Boxer *)box:(void *)aPtr withType:(const char *)aType copy:(BOOL)aShouldCopy
{
// Check if this type has been handled already
const char *className = [[self _classNameForType:aType] UTF8String];
Class boxingClass = objc_getClass(className);
if(boxingClass)
return [boxingClass box:aPtr];
// Seems it hasn't. Let's.
if([self typeIsScalar:aType])
boxingClass = [self _prepareScalar:className withType:aType];
else
boxingClass = [self _prepareAggregate:className withType:aType];
objc_registerClassPair(boxingClass);
return [boxingClass box:aPtr];
}
+ (id)box:(void *)aPtr
{
return [[[self allocWithZone:NULL] initWithPtr:aPtr] autorelease];
}
+ (void)unbox:(id)aValue to:(void *)aDest usingType:(const char *)aType
{
if([self typeIsScalar:aType]) {
switch(*aType) {
case _C_ID:
case _C_CLASS:
*(id*)aDest = aValue;
break;
case _C_SEL:
*(SEL*)aDest = NSSelectorFromString(aValue);
break;
case _C_CHARPTR:
*(const char **)aDest = [(NSString *)aValue UTF8String];
break;
case _C_DBL:
*(double *)aDest = [(NSNumber *)aValue doubleValue];
break;
case _C_FLT:
*(float *)aDest = [(NSNumber *)aValue floatValue];
break;
case _C_INT:
*(int *)aDest = [(NSNumber *)aValue intValue];
break;
case _C_SHT:
*(short *)aDest = [(NSNumber *)aValue shortValue];
break;
case _C_BOOL:
*(BOOL *)aDest = [(NSNumber *)aValue boolValue];
break;
case _C_LNG:
*(long *)aDest = [(NSNumber *)aValue longValue];
break;
case _C_LNG_LNG:
*(long long *)aDest = [(NSNumber *)aValue longLongValue];
break;
case _C_UINT:
*(unsigned int *)aDest = [(NSNumber *)aValue unsignedIntValue];
break;
case _C_USHT:
*(unsigned short *)aDest = [(NSNumber *)aValue unsignedShortValue];
break;
case _C_ULNG:
*(unsigned long *)aDest = [(NSNumber *)aValue unsignedLongValue];
break;
case _C_ULNG_LNG:
*(unsigned long long *)aDest = [(NSNumber *)aValue unsignedLongLongValue];
break;
default:
NSLog(@"Unsupported scalar type %c!", *aType);
return;
}
} else {
assert([aValue isKindOfClass:self]);
NSUInteger size;
NSGetSizeAndAlignment(aType, &size, NULL);
Boxer *value = aValue;
assert(value->_size == size);
memmove(aDest, value->_ptr, size);
}
}
- (id)initWithPtr:(void *)aPtr
{
[NSException raise:@"Invalid Receiver" format:@"Boxer is an abstract class. Do not try to instantiate it directly."];
// Implemented by subclasses
return nil;
}
- (void)dealloc
{
if(_isOnHeap)
free(_ptr);
[super dealloc];
}
- (void)_moveValueToHeap
{
void *stackAddr = _ptr;
_ptr = malloc(_size);
memmove(_ptr, stackAddr, _size);
_isOnHeap = YES;
}
- (id)retain
{
id ret = [super retain];
[self _moveValueToHeap];
return ret;
}
#pragma mark -
+ (NSString *)_getFieldName:(const char **)aType
{
if(*(*aType) != '"')
return NULL;
*aType = *aType + 1;
const char *nameEnd = strstr(*aType, "\"");
int len = nameEnd - *aType;
NSString *ret = [[NSString alloc] initWithBytes:*aType length:len encoding:NSUTF8StringEncoding];
(*aType) += len+1;
return [ret autorelease];
}
+ (Class)_prepareAggregate:(const char *)aClassName withType:(const char *)aType
{
Class kls = objc_allocateClassPair(self, aClassName, 0);
NSUInteger size, alignment;
NSGetSizeAndAlignment(aType, &size, &alignment);
class_addIvar(kls, "_fields", sizeof(id), log2(sizeof(id)), "@");
// Store the accessors sequentially in order to allow indexed access (necessary for structs without field name information)
NSMutableArray *fieldGetters = [NSMutableArray array];
NSMutableArray *fieldSetters = [NSMutableArray array];
id fieldGetter, fieldSetter;
NSUInteger fieldSize, fieldAlignment, fieldOffset;
fieldOffset = 0;
const char *nextType;
const char *fieldType = strstr(aType, "=")+1;
// Add properties for each field
while((nextType = NSGetSizeAndAlignment(fieldType, &fieldSize, &fieldAlignment))) {
NSString *name = [self _getFieldName:&fieldType];
fieldGetter = [^(Boxer *self) {
return [Boxer box:self->_ptr+fieldOffset withType:fieldType];
} copy];
fieldSetter = [^(Boxer *self, id value) {
[Boxer unbox:value to:self->_ptr+fieldOffset usingType:fieldType];
} copy];
if(name) {
class_addMethod(kls, NSSelectorFromString(name), BlockImp(fieldGetter), "@:");
class_addMethod(kls, NSSelectorFromString([NSString stringWithFormat:@"set%@:", [name capitalizedString]]), BlockImp(fieldSetter), "@:");
}
[fieldGetters addObject:fieldGetter];
[fieldSetters addObject:fieldSetter];
if(strlen(nextType) == 0 || *nextType == _C_STRUCT_E)
break;
fieldOffset += fieldSize;
fieldType = nextType;
}
// Persistent immutable copies shared amongst instances
fieldGetters = [fieldGetters copy];
fieldSetters = [fieldSetters copy];
IMP subscriptGetterImp = BlockImp(^(id self, NSInteger idx) {
id (^getter)(id) = [fieldGetters objectAtIndex:idx];
return getter(self);
});
const char *subscrGetterType = [[NSString stringWithFormat:@"@:%s", @encode(NSInteger)] UTF8String];
class_addMethod(kls, @selector(objectAtIndexedSubscript:), subscriptGetterImp, subscrGetterType);
IMP subscriptSetterImp = BlockImp(^(id self, id value, NSInteger idx) {
id (^setter)(id, id) = [fieldSetters objectAtIndex:idx];
return setter(self, value);
});
const char *subscrSetterType = [[NSString stringWithFormat:@"@:@%s", @encode(NSInteger)] UTF8String];
class_addMethod(kls, @selector(setObject:atIndexedSubscript:), subscriptSetterImp, subscrSetterType);
IMP initImp = BlockImp(^(Boxer *self, void *aPtr) {
self->_ptr = aPtr;
self->_size = size;
return self;
});
class_addMethod(kls, @selector(initWithPtr:), initImp, "@:*");
return kls;
}
+ (BOOL)typeIsScalar:(const char *)aType
{
return !(*aType == _C_STRUCT_B || *aType == _C_UNION_B || *aType == _C_ARY_B || *aType == _C_PTR);
}
+ (const char *)_findClosingBrace:(const char *)aStr
{
for(int i = 0, depth = 0; i < strlen(aStr); ++i) {
if(aStr[i] == _C_STRUCT_B)
++depth;
else if(aStr[i] == _C_STRUCT_E) {
if(--depth == 0)
return aStr+i;
}
}
return NULL;
}
+ (NSString *)_classNameForType:(const char *)aType
{
NSUInteger len;
if(*aType == _C_STRUCT_B)
len = [self _findClosingBrace:aType] - aType + 1;
else
len = 1;
len += strlen(BOXER_PREFIX) + 1;
char className[len+1];
snprintf(className, len, "%s%s", BOXER_PREFIX, aType);
return [NSString stringWithUTF8String:className];
}
+ (Class)_prepareScalar:(const char *)aClassName withType:(const char *)aType
{
NSUInteger size, alignment;
NSGetSizeAndAlignment(aType, &size, &alignment);
IMP initImp = nil;
Class superClass = self;
switch(*aType) {
case _C_ID:
case _C_CLASS:
initImp = BlockImp(^(Boxer *self, id *aPtr) { return *aPtr; });
break;
case _C_SEL:
initImp = BlockImp(^(Boxer *self, SEL *aPtr) { return NSStringFromSelector(*aPtr); });
break;
case _C_CHARPTR:
initImp = BlockImp(^(Boxer *self, const char *aPtr) { return @(*aPtr); });
break;
case _C_DBL:
initImp = BlockImp(^(Boxer *self, double *aPtr) { return @(*aPtr); });
break;
case _C_FLT:
initImp = BlockImp(^(Boxer *self, float *aPtr) { return @(*aPtr); });
break;
case _C_INT:
initImp = BlockImp(^(Boxer *self, int *aPtr) { return @(*aPtr); });
break;
case _C_SHT:
initImp = BlockImp(^(Boxer *self, short *aPtr) { return @(*aPtr); });
break;
case _C_BOOL:
initImp = BlockImp(^(Boxer *self, _Bool *aPtr) { return @(*aPtr); });
break;
case _C_LNG:
initImp = BlockImp(^(Boxer *self, long *aPtr) { return @(*aPtr); });
break;
case _C_LNG_LNG:
initImp = BlockImp(^(Boxer *self, long long *aPtr) { return @(*aPtr); });
break;
case _C_UINT:
initImp = BlockImp(^(Boxer *self, unsigned int *aPtr) { return @(*aPtr); });
break;
case _C_USHT:
initImp = BlockImp(^(Boxer *self, unsigned short *aPtr) { return @(*aPtr); });
break;
case _C_ULNG:
initImp = BlockImp(^(Boxer *self, unsigned long *aPtr) { return @(*aPtr); });
break;
case _C_ULNG_LNG:
initImp = BlockImp(^(Boxer *self, unsigned long long *aPtr) { return @(*aPtr); });
break;
default:
NSLog(@"Unsupported scalar type %c!", *aType);
return nil;
}
Class kls = objc_allocateClassPair(superClass, aClassName, 0);
class_addMethod(kls->isa, @selector(box:), initImp, "@:*");
return kls;
}
@end
#define BOX(val) [Boxer box:&(val) withType:@encode(__typeof(val))]
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
CGRect myRect = {1,2,3,4};
Boxer *boxedRect = [Boxer box:&myRect withType:"{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}"];
[[boxedRect origin] setX:@1337];
CGSize size = {99,100};
[boxedRect setSize:BOX(size)];
NSLog(@"boxed rect: %@ ((%@,%@)(%@,%@))", boxedRect, [[boxedRect origin] x], [[boxedRect origin] y], [[boxedRect size] width], [[boxedRect size] height]);
NSLog(@"Retrieved back: %@ %@", NSStringFromRect(*(CGRect*)boxedRect.valuePtr), NSStringFromRect(myRect));
NSLog(@"indexed access %@", [boxedRect objectAtIndexedSubscript:1]);
double dbl = 1.234;
int integ = 1234;
NSLog(@"Boxed double: %@ int: %@", BOX(dbl), BOX(integ));
[p release];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment