Last active
August 29, 2015 14:05
-
-
Save fjolnir/834e7fa1a73371246d49 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> | |
#import <ffi/ffi.h> | |
#import <sys/mman.h> | |
// Wrapper closure called as a result of a message to `initWith..` | |
static void _ai_closure(ffi_cif * const aCif, id * const aoRet, void * const aArgs[], NSArray * const paramInfo); | |
static ffi_closure *_ai_ffi_allocClosure(void ** const codePtr); | |
static ffi_status _ai_ffi_prepareClosure(ffi_closure * const closure, ffi_cif * const cif, | |
void (*fun)(ffi_cif*,void*,void**,void*), | |
void * const user_data, void * const codeloc); | |
static ffi_type *_ai_encodingToFFIType(const char *aEncoding); | |
static ffi_type *_ai_scalarTypeToFFIType(const char * const aType); | |
static BOOL _ai_typeIsNumeric(const char * const aType); | |
@interface NSObject (AutomaticInitializers) | |
@end | |
@interface NSNumber (AutomaticInitializers) | |
+ (NSNumber *)ai_numberWithBytes:(const void *)aValue objCType:(const char *)aType; | |
@end | |
@implementation NSObject (AutomaticInitializers) | |
+ (void)load | |
{ | |
method_exchangeImplementations(class_getClassMethod([NSObject class], @selector(resolveInstanceMethod:)), | |
class_getClassMethod([NSObject class], @selector(_ai_resolveInstanceMethod:))); | |
} | |
+ (BOOL)_ai_resolveInstanceMethod:(SEL const)aSel | |
{ | |
NSString * const sel = NSStringFromSelector(aSel); | |
if([sel hasPrefix:@"initWith"] && [sel hasSuffix:@":"]) { | |
NSScanner * const scanner = [NSScanner scannerWithString:sel]; | |
scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@":"]; | |
scanner.scanLocation = 8; | |
NSMutableArray * const properties = [NSMutableArray new]; | |
NSMutableString * const typeEncoding = [@"@:" mutableCopy]; | |
NSString *propertyName; | |
while([scanner scanUpToString:@":" intoString:&propertyName]) { | |
propertyName = [propertyName stringByReplacingCharactersInRange:(NSRange) {0, 1} | |
withString:[[propertyName substringToIndex:1] lowercaseString]]; | |
objc_property_t const property = class_getProperty(self, [propertyName UTF8String]); | |
if(!property) { | |
NSLog(@"No %@ found", propertyName); | |
goto noInitializer; | |
} | |
// Scan for the type encoding | |
NSScanner * const attrScanner = [NSScanner scannerWithString:@(property_getAttributes(property))]; | |
[attrScanner scanUpToString:@"T" intoString:NULL]; | |
attrScanner.scanLocation += 1; | |
NSString *encoding; | |
if([attrScanner scanUpToString:@"," intoString:&encoding]) { | |
[typeEncoding appendString:encoding]; | |
[properties addObject:@{ @"name": @(property_getName(property)), | |
@"encoding": encoding, | |
@"pointer": [NSValue valueWithPointer:property] }]; | |
} else { | |
[NSException raise:NSInternalInconsistencyException | |
format:@"Unable to parse attributes for property %@", propertyName]; | |
goto noInitializer; | |
} | |
} | |
// Scan the property attributes for types | |
ffi_type ** const parameterTypes = malloc(sizeof(void*) * ([properties count] + 2)); | |
parameterTypes[0] = parameterTypes[1] = &ffi_type_pointer; | |
for(NSUInteger i = 0; i < [properties count]; ++i) { | |
parameterTypes[i+2] = _ai_encodingToFFIType([properties[i][@"encoding"] UTF8String]); | |
} | |
// Create the IMP | |
void *imp; | |
ffi_closure *closure = _ai_ffi_allocClosure(&imp); | |
if(!closure) { | |
[NSException raise:NSInternalInconsistencyException | |
format:@"Failed to allocate closure for %@", sel]; | |
goto noInitializer; | |
} | |
ffi_cif * const cif = malloc(sizeof(ffi_cif)); | |
if(ffi_prep_cif(cif, FFI_DEFAULT_ABI, | |
(unsigned int)[properties count] + 2, | |
&ffi_type_pointer, | |
parameterTypes) == FFI_OK | |
&& _ai_ffi_prepareClosure(closure, cif, | |
(void (*)(ffi_cif*,void*,void**,void*))_ai_closure, | |
(__bridge_retained void*)properties, | |
imp) == FFI_OK) | |
{ | |
class_addMethod(self, aSel, imp, [typeEncoding UTF8String]); | |
return YES; | |
} else { | |
free(cif); | |
[NSException raise:NSInternalInconsistencyException | |
format:@"Failed to perpare closure for %@", sel]; | |
goto noInitializer; | |
} | |
} | |
noInitializer: | |
return [self _ai_resolveInstanceMethod:aSel]; | |
} | |
@end | |
void _ai_closure(ffi_cif * const aCif, id * const aoRet, void * const aArgs[], NSArray * const properties) | |
{ | |
id const object = [*(__strong id *)aArgs[0] init]; | |
for(unsigned int i = 2; i < aCif->nargs; ++i) { | |
NSDictionary * const property = properties[i-2]; | |
const char * const encoding = [property[@"encoding"] UTF8String]; | |
id const box = *encoding == _C_ID | |
? *(__strong id *)aArgs[i] | |
: _ai_typeIsNumeric(encoding) | |
? [NSNumber ai_numberWithBytes:aArgs[i] objCType:[property[@"encoding"] UTF8String]] | |
: [NSValue valueWithBytes:aArgs[i] objCType:[property[@"encoding"] UTF8String]]; | |
[object setValue:box forKey:property[@"name"]]; | |
} | |
*aoRet = object; | |
} | |
#pragma mark - FFI & Type wrangling | |
static ffi_closure *_ai_ffi_allocClosure(void ** const codePtr) | |
{ | |
ffi_closure *closure = (ffi_closure *)mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); | |
if(closure == (void *)-1) { | |
perror("mmap"); | |
return NULL; | |
} | |
*codePtr = closure; | |
return closure; | |
} | |
static ffi_status _ai_ffi_prepareClosure(ffi_closure * const closure, ffi_cif * const cif, | |
void (*fun)(ffi_cif*,void*,void**,void*), | |
void * const user_data, void * const codeloc) | |
{ | |
ffi_status status = ffi_prep_closure(closure, cif, fun, user_data); | |
if(status != FFI_OK) | |
return status; | |
if(mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1) | |
return (ffi_status)1; | |
return FFI_OK; | |
} | |
BOOL _ai_type_is_scalar(const char * const aType) | |
{ | |
return !(*aType == _C_STRUCT_B || *aType == _C_UNION_B || *aType == _C_ARY_B); | |
} | |
ffi_type *_ai_encodingToFFIType(const char *aEncoding) | |
{ | |
if(_ai_type_is_scalar(aEncoding)) | |
return _ai_scalarTypeToFFIType(aEncoding); | |
else if(*aEncoding == _C_STRUCT_B || *aEncoding ==_C_ARY_B) { | |
BOOL const isArray = *aEncoding ==_C_ARY_B; | |
ffi_type * const type = malloc(sizeof(ffi_type)); | |
type->type = FFI_TYPE_STRUCT; | |
type->size = type->alignment= 0; | |
int numFields = 0; | |
const char *fieldEncoding; | |
if(isArray) { | |
++aEncoding; // Skip past the '[' | |
assert(isdigit(*aEncoding)); | |
numFields = atoi(aEncoding); | |
// Move on to the enclosed type | |
while(isdigit(*aEncoding)) ++aEncoding; | |
fieldEncoding = aEncoding; | |
} else { // Is struct | |
fieldEncoding = strstr(aEncoding, "=") + 1; | |
if(*fieldEncoding != _C_STRUCT_E) { | |
const char *currField = fieldEncoding; | |
do { | |
++numFields; | |
} while((currField = NSGetSizeAndAlignment(currField, NULL, NULL)) && *currField != _C_STRUCT_E); | |
} | |
} | |
type->elements = (ffi_type **)malloc(sizeof(ffi_type*) * numFields + 1); | |
if(isArray) { | |
ffi_type *fieldType = _ai_encodingToFFIType(fieldEncoding); | |
assert(fieldType); | |
for(int i = 0; i < numFields; i++) { | |
type->elements[i] = fieldType; | |
} | |
} else { | |
for(int i = 0; i < numFields; i++) { | |
ffi_type *fieldType = _ai_encodingToFFIType(fieldEncoding); | |
assert(fieldType); | |
type->elements[i] = fieldType; | |
fieldEncoding = NSGetSizeAndAlignment(fieldEncoding, NULL, NULL); | |
} | |
} | |
type->elements[numFields] = NULL; | |
return type; | |
} | |
else if(*aEncoding == _C_UNION_B) { | |
// For unions we just return the ffi type for the first element in the union | |
// TODO: this should use the largest type | |
const char *fieldEncoding = strstr(aEncoding, "=") + 1; | |
return _ai_encodingToFFIType(fieldEncoding); | |
} else { | |
[NSException raise:NSInternalInconsistencyException format:@"Unhandled type encoding %s", aEncoding]; | |
return NULL; | |
} | |
} | |
BOOL _ai_typeIsNumeric(const char * const aType) | |
{ | |
char const type = *aType; | |
return type == _C_DBL || type == _C_FLT || type == _C_INT | |
|| type == _C_SHT || type == _C_CHR || type == _C_BOOL | |
|| type == _C_LNG || type == _C_LNG_LNG || type == _C_UINT | |
|| type == _C_USHT || type == _C_UCHR || type == _C_ULNG | |
|| type == _C_ULNG_LNG; | |
} | |
ffi_type *_ai_scalarTypeToFFIType(const char * const aType) | |
{ | |
switch(*aType) { | |
case _C_ID: | |
case _C_CLASS: | |
case _C_SEL: | |
case _C_PTR: | |
case _C_CHARPTR: return &ffi_type_pointer; | |
case _C_DBL: return &ffi_type_double; | |
case _C_FLT: return &ffi_type_float; | |
case _C_INT: return &ffi_type_sint; | |
case _C_SHT: return &ffi_type_sshort; | |
case _C_CHR: return &ffi_type_sint8; | |
case _C_BOOL: return &ffi_type_uchar; | |
case _C_LNG: return &ffi_type_slong; | |
case _C_LNG_LNG: return &ffi_type_sint64; | |
case _C_UINT: return &ffi_type_uint; | |
case _C_USHT: return &ffi_type_ushort; | |
case _C_UCHR: return &ffi_type_uchar; | |
case _C_ULNG: return &ffi_type_ulong; | |
case _C_ULNG_LNG: return &ffi_type_uint64; | |
case _C_VOID: return &ffi_type_void; | |
default: | |
[NSException raise:NSGenericException | |
format:@"Unsupported scalar type %c!", *aType]; | |
return NULL; | |
} | |
} | |
@implementation NSNumber (AutoInitializers) | |
+ (NSNumber *)ai_numberWithBytes:(const void * const )aValue | |
objCType:(const char * const )aType | |
{ | |
NSParameterAssert(aType && aValue); | |
switch(*aType) { | |
case _C_DBL: | |
return @(*(double *)aValue); | |
case _C_FLT: | |
return @(*(float *)aValue); | |
case _C_INT: | |
return @(*(int *)aValue); | |
case _C_SHT: | |
return @(*(short *)aValue); | |
case _C_CHR: | |
return @(*(char *)aValue); | |
case _C_BOOL: | |
return @(*(BOOL *)aValue); | |
case _C_LNG: | |
return @(*(long *)aValue); | |
case _C_LNG_LNG: | |
return @(*(long long *)aValue); | |
case _C_UINT: | |
return @(*(unsigned int *)aValue); | |
case _C_USHT: | |
return @(*(unsigned short *)aValue); | |
case _C_UCHR: | |
return @(*(unsigned char *)aValue); | |
case _C_ULNG: | |
return @(*(unsigned long *)aValue); | |
case _C_ULNG_LNG: | |
return @(*(unsigned long long *)aValue); | |
default: | |
[NSException raise:NSGenericException | |
format:@"Non-numeric type %c!", *aType]; | |
return nil; | |
} | |
} | |
@end |
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> | |
@interface Klass : NSObject | |
@property int num; | |
@property id obj; | |
@property NSRect rect; | |
@end | |
@interface Klass (Initializers) | |
- (instancetype)initWithNum:(NSUInteger)aNum obj:(id)aObj rect:(CGRect)aRect; | |
@end | |
@implementation Klass | |
@end | |
int main(int argc, char *argv[]) { | |
@autoreleasepool { | |
Klass * const test = [[Klass alloc] initWithNum:123 | |
obj:@456 | |
rect:(CGRect) { {7,8},{9,10} }]; | |
NSLog(@"%@: %d %@ %@", test, (int)test.num, test.obj, NSStringFromRect(test.rect)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment