Created
July 11, 2012 08:14
-
-
Save fjolnir/3088888 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#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