Created
July 9, 2013 20:55
-
-
Save markd2/5961219 to your computer and use it in GitHub Desktop.
Objective-C runtime metadata dumper.
This file contains 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 "typestring.h" | |
// clang -g -fobjc-arc -Wall -framework Foundation -o runtime typestring.m runtime.m | |
// Runtime reference, at least until Apple breaks the link | |
// http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html | |
static NSString *ReadableTypeString (NSString *chunk); | |
@protocol XXSnork <NSObject> | |
@required | |
- (void) somethingRequired: (NSString *) blorp; | |
+ (CGRect) classicallyRequired: (short) fuse; | |
@optional | |
+ (void) classicallyOptional: (NSAffineTransform *) robotsInTheSkies; | |
- (void) somethingOptional1; | |
- (void) somethingOptional2; | |
@end // XXSnork | |
@interface XXStuff : NSObject <XXSnork> | |
@property (assign, nonatomic) CGRect bounds; | |
@property (assign, nonatomic) CGRect bounds2; | |
@property (assign, nonatomic) CGRect *boundsPointer; | |
@property (weak, setter=getBlah:, getter=setBlah) NSString *blah; | |
@property (assign, nonatomic) unsigned long long shoeSize; | |
@property (weak, nonatomic) id <NSObject> delegate; | |
@property (strong) NSURL *mikeysPonyFarm; | |
@property (assign, nonatomic) CGFloat gilliganFactor; | |
@property (copy) void (^ook)(void); | |
@property (copy) void (^blockHead)(NSFileHandle *); | |
- (void) blahBlahBlah: (id<NSObject>) ook; | |
- (void) downloadFanFicWithCompletion: (BOOL (^) (NSURL *url, NSError *error)) completion; | |
- (NSString *) blahWithBitmapDataPlanes: (unsigned char **) planes | |
pixelsWide: (NSInteger) width | |
pixelsHigh: (NSInteger) height | |
bitsPerSample: (NSInteger) bps | |
samplesPerPixel: (NSInteger) spp | |
hasAlpha: (BOOL) alpha | |
isPlanar: (BOOL) isPlanar | |
colorSpaceName: (NSString *) colorSpaceName | |
bytesPerRow: (NSInteger) rBytes | |
bitsPerPixel: (NSInteger) pBits; | |
@end // XXStuff | |
@interface XXStuff (Category) | |
- (void) categoriesRUs; | |
@end // XXStuff | |
@implementation XXStuff | |
@dynamic bounds; | |
@synthesize blah = _scoobyDoobyDoo; | |
+ (void) aClassMethod { } | |
+ (CGRect) classicallyRequired: (short) fuse { return (CGRect){ {1, 2}, {3, 4} }; } | |
+ (void) classicallyOptional: (NSAffineTransform *) robotsInTheSkies { } | |
- (void) anInstanceMethod { } | |
- (void) greebleBork { } | |
- (void) somethingRequired: (NSString *) blorp { } | |
- (void) blahBlahBlah: (id<NSObject>) ook { } | |
- (void) downloadFanFicWithCompletion: (BOOL (^) (NSURL *url, NSError *error)) completion { } | |
- (NSString *) blahWithBitmapDataPlanes: (unsigned char **) planes | |
pixelsWide: (NSInteger) width | |
pixelsHigh: (NSInteger) height | |
bitsPerSample: (NSInteger) bps | |
samplesPerPixel: (NSInteger) spp | |
hasAlpha: (BOOL) alpha | |
isPlanar: (BOOL) isPlanar | |
colorSpaceName: (NSString *) colorSpaceName | |
bytesPerRow: (NSInteger) rBytes | |
bitsPerPixel: (NSInteger) pBits { | |
return @"this method name is too damn long!"; | |
} | |
@end // XXStuff | |
static void ListClasses (void) { | |
#define OLD_SCHOOL 1 | |
#if OLD_SCHOOL | |
unsigned int classCount = objc_getClassList (NULL, 0); | |
Class *classes = (__unsafe_unretained Class *) malloc (sizeof(Class) * classCount); | |
unsigned int newClassCount = objc_getClassList (classes, classCount); | |
assert (classCount == newClassCount); | |
#else | |
unsigned int classCount; | |
Class *classes = objc_copyClassList (&classCount); | |
#endif | |
// Sort by name | |
qsort_b (classes, classCount, sizeof(Class), | |
^(const void *thing1, const void *thing2) { | |
return strcmp(class_getName(*((Class *)thing1)), | |
class_getName(*((Class *)thing2))); | |
}); | |
printf ("Got %d classes\n", classCount); | |
for (int i = 0; i < classCount; i++) { | |
const char *name = class_getName (classes[i]); | |
unsigned int propertyCount; | |
objc_property_t *properties = class_copyPropertyList (classes[i], &propertyCount); | |
free (properties); | |
unsigned int ivarCount; | |
Ivar *ivars = class_copyIvarList (classes[i], &ivarCount); | |
free (ivars); | |
unsigned int protocolCount; | |
Protocol * __unsafe_unretained *protocols = class_copyProtocolList (classes[i], &protocolCount); | |
free (protocols); | |
unsigned int instanceMethodCount; | |
unsigned int classMethodCount; | |
Method *instanceMethods = class_copyMethodList (classes[i], &instanceMethodCount); | |
Method *classMethods = class_copyMethodList (object_getClass(classes[i]), &classMethodCount); | |
free (instanceMethods); | |
free (classMethods); | |
printf (" %d: %s, %d properties, %d ivars, %d protocols, " | |
"%d instance methods, %d class methods\n", | |
i, name, propertyCount, ivarCount, protocolCount, | |
instanceMethodCount, classMethodCount); | |
} | |
free (classes); | |
} // ListClasses | |
static void ListProtocols (void) { | |
unsigned int protocolCount; | |
Protocol *__unsafe_unretained *protocols = objc_copyProtocolList (&protocolCount); | |
printf ("Got %d protocols\n", protocolCount); | |
for (int i = 0; i < protocolCount; i++) { | |
const char *protocolName = protocol_getName (protocols[i]); | |
unsigned int adopteeCount; | |
Protocol * __unsafe_unretained *adoptees = | |
protocol_copyProtocolList (protocols[i], &adopteeCount); | |
free (adoptees); | |
struct objc_method_description *methods; | |
unsigned int count; | |
unsigned int requiredCount = 0; | |
unsigned int optionalCount = 0; | |
methods = protocol_copyMethodDescriptionList (protocols[i], YES, YES, &count); | |
requiredCount += count; | |
free (methods); | |
methods = protocol_copyMethodDescriptionList (protocols[i], YES, NO, &count); | |
requiredCount += count; | |
free (methods); | |
methods = protocol_copyMethodDescriptionList (protocols[i], NO, YES, &count); | |
optionalCount += count; | |
free (methods); | |
methods = protocol_copyMethodDescriptionList (protocols[i], NO, NO, &count); | |
optionalCount += count; | |
free (methods); | |
printf (" %d: %s, %d adoptees, %d required, %d optional\n", | |
i, protocolName, adopteeCount, requiredCount, optionalCount); | |
} | |
free (protocols); | |
} // ListProtocols | |
static NSString *ReadableTypeString (NSString *typestring) { | |
NSArray *chunks = ParseTypeString (typestring); | |
NSString *result = [chunks componentsJoinedByString: @", "]; | |
return result; | |
} // ReadableTypeString | |
static const char *ReadablePropertyAttributes (const char *attributeCString) { | |
NSString *attributeString = @( attributeCString ); | |
NSArray *chunks = [attributeString componentsSeparatedByString: @","]; | |
NSMutableArray *translatedChunks = [NSMutableArray arrayWithCapacity: chunks.count]; | |
NSString *string; | |
for (NSString *chunk in chunks) { | |
unichar first = [chunk characterAtIndex: 0]; | |
switch (first) { | |
case 'T': // encode type. @ has class name after it | |
string = ReadableTypeString ([chunk substringFromIndex: 1]); | |
[translatedChunks addObject: string]; | |
break; | |
case 'V': // backing ivar name | |
string = [NSString stringWithFormat: @"ivar: %@", | |
[chunk substringFromIndex: 1]]; | |
[translatedChunks addObject: string]; | |
break; | |
case 'R': // read-only | |
[translatedChunks addObject: @"readonly"]; | |
break; | |
case 'C': // copy | |
[translatedChunks addObject: @"copy"]; | |
break; | |
case '&': // retain | |
[translatedChunks addObject: @"retain/strong"]; | |
break; | |
case 'N': // non-atomic | |
[translatedChunks addObject: @"non-atomic"]; | |
break; | |
case 'G': // custom getter | |
string = [NSString stringWithFormat: @"getter: %@", | |
[chunk substringFromIndex: 1]]; | |
[translatedChunks addObject: string]; | |
break; | |
case 'S': // custom setter | |
string = [NSString stringWithFormat: @"setter: %@", | |
[chunk substringFromIndex: 1]]; | |
[translatedChunks addObject: string]; | |
break; | |
case 'D': // dynamic | |
[translatedChunks addObject: @"dynamic"]; | |
break; | |
case 'W': // weak | |
[translatedChunks addObject: @"weak"]; | |
break; | |
case 'P': // eligible for GC | |
[translatedChunks addObject: @"GC"]; | |
break; | |
case 't': // old-style encoding | |
[translatedChunks addObject: chunk]; | |
break; | |
default: | |
[translatedChunks addObject: chunk]; | |
break; | |
} | |
} | |
NSString *result = [translatedChunks componentsJoinedByString: @", "]; | |
return [result UTF8String]; | |
} // ReadablePropertyAttributes | |
static void ClassInfo (const char *classname) { | |
Class classy = objc_getClass (classname); | |
if (classy == nil) { | |
printf ("could not find class named '%s'\n", classname); | |
exit (1); | |
} | |
printf ("Interesting stuff about %s\n", classname); | |
Class superclass = class_getSuperclass (classy); | |
if (superclass == nil) { | |
printf ("* %s has no superclass\n", classname); | |
} else { | |
printf ("* %s is a subclass of %s\n", classname, class_getName (superclass)); | |
} | |
int version = class_getVersion (classy); | |
printf ("* is at version %d\n", version); | |
// Classes with non-zero versions: | |
// NSAffineTransform, NSCountedSet, NSDateFormatter, | |
// NSMutableString, NSNumberFormatter, NSString | |
size_t instanceSize = class_getInstanceSize (classy); | |
printf ("* is %zu bytes large\n", instanceSize); | |
unsigned int propertyCount; | |
objc_property_t *properties = class_copyPropertyList (classy, &propertyCount); | |
printf ("* has %d properties\n", propertyCount); | |
// For fun, walk this pointer-style | |
objc_property_t *propertyScan = properties; | |
while (*propertyScan) { | |
const char *propertyName = property_getName (*propertyScan); | |
const char *propertyAttributes = property_getAttributes (*propertyScan); | |
printf (" - %s -> %s\n", propertyName, | |
ReadablePropertyAttributes(propertyAttributes)); | |
propertyScan++; | |
} | |
free (properties); | |
unsigned int ivarCount; | |
Ivar *ivars = class_copyIvarList (classy, &ivarCount); | |
printf ("* has %d ivars\n", ivarCount); | |
for (int i = 0; i < ivarCount; i++) { | |
const char *ivarName = ivar_getName (ivars[i]); | |
ptrdiff_t ivarOffset = ivar_getOffset (ivars[i]); | |
const char *ivarEncoding = ivar_getTypeEncoding (ivars[i]); | |
printf (" - %s -> offset %d, %s\n", ivarName, (int)ivarOffset, | |
[ReadableTypeString(@(ivarEncoding)) UTF8String]); | |
} | |
free (ivars); | |
unsigned int protocolCount; | |
Protocol * __unsafe_unretained *protocols = class_copyProtocolList (classy, &protocolCount); | |
printf ("* has %d protocols\n", protocolCount); | |
for (int i = 0; i < protocolCount; i++) { | |
const char *protocolName = protocol_getName (protocols[i]); | |
unsigned int requiredMethodCount; | |
unsigned int optionalMethodCount; | |
struct objc_method_description *requiredMethods = | |
protocol_copyMethodDescriptionList (protocols[i], YES, YES, &requiredMethodCount); | |
free (requiredMethods); | |
struct objc_method_description *optionalMethods = | |
protocol_copyMethodDescriptionList (protocols[i], NO, YES, &optionalMethodCount); | |
free (optionalMethods); | |
printf (" - %s -> %d required, %d optional\n", protocolName, | |
requiredMethodCount, optionalMethodCount); | |
} | |
free (protocols); | |
} // ClassInfo | |
static void PrintMethods (Method methods[], BOOL isInstance) { | |
while (*methods) { | |
SEL selector = method_getName(*methods); | |
const char *name = sel_getName(selector); | |
// This returns another kind of encoding string : e.g. Q16@0:8 with sizes | |
// and offsets. Which according to email threads are wrong and should be | |
// ignored | |
// http://lists.apple.com/archives/objc-language/2009/Apr/msg00184.html | |
char buffer[100]; | |
buffer[0] = '\0'; | |
method_getReturnType (*methods, buffer, sizeof(buffer)); | |
NSString *returnTypeString = ReadableTypeString (@(buffer)); | |
unsigned int argumentCount = method_getNumberOfArguments (*methods); | |
NSMutableArray *arguments = [NSMutableArray arrayWithCapacity: argumentCount]; | |
// Skip over self and selector. | |
for (int i = 2; i < argumentCount; i++) { | |
method_getArgumentType (*methods, i, buffer, sizeof(buffer)); | |
[arguments addObject: ReadableTypeString (@(buffer))]; | |
} | |
NSString *argumentString = @"nothing"; | |
if (arguments.count > 0) { | |
argumentString = [arguments componentsJoinedByString: @", "]; | |
} | |
printf (" %s %s : returns %s and takes %s\n", (isInstance) ? "-" : "+", | |
name, returnTypeString.UTF8String, argumentString.UTF8String); | |
methods++; | |
} | |
} // PrintMethods | |
static void MethodInfo (const char *classname) { | |
Class classy = objc_getClass (classname); | |
if (classy == nil) { | |
printf ("could not find class named '%s'\n", classname); | |
exit (1); | |
} | |
unsigned int instanceMethodCount; | |
unsigned int classMethodCount; | |
Method *instanceMethods = class_copyMethodList (classy, &instanceMethodCount); | |
Method *classMethods = class_copyMethodList (object_getClass(classy), &classMethodCount); | |
printf ("%d Instance Methods for %s\n", instanceMethodCount, classname); | |
PrintMethods (instanceMethods, YES); | |
printf ("%d Class Methods for %s\n", classMethodCount, classname); | |
PrintMethods (classMethods, NO); | |
free (instanceMethods); | |
free (classMethods); | |
} // MethodInfo | |
static void DumpMethodDescriptions (struct objc_method_description *methods, | |
unsigned int count, char lineDecoration) { | |
for (int i = 0; i < count; i++) { | |
const char *name = sel_getName(methods[i].name); | |
NSString *typeString = @( methods[i].types ); | |
NSArray *types = ParseTypeString (typeString); | |
NSString *returnType = [types objectAtIndex: 0]; | |
NSArray *noSelfAndSelector = | |
[types subarrayWithRange: (NSRange) {3, types.count - 3}]; | |
NSString *typeDescription = [noSelfAndSelector componentsJoinedByString: @" "]; | |
if (typeDescription.length == 0) typeDescription = @"nothing"; | |
printf (" %c %s -> takes %s, returns %s\n", lineDecoration, name, | |
typeDescription.UTF8String, returnType.UTF8String); | |
} | |
} // DumpMethodDescriptions | |
static void ProtocolInfo (const char *protocolName) { | |
// There is no "look up protocol by name" like there is for classes (which is | |
// generally useful), so grab all the protocols and sift through them looking | |
// for |protocolName| | |
unsigned int protocolCount; | |
Protocol * __unsafe_unretained *protocols = objc_copyProtocolList (&protocolCount); | |
Protocol *protocol = NULL; | |
for (int i = 0; i < protocolCount; i++) { | |
const char *thisName = protocol_getName (protocols[i]); | |
if (strcmp(protocolName, thisName) == 0) { | |
protocol = protocols[i]; | |
break; | |
} | |
} | |
free (protocols); | |
if (!protocol) { | |
printf ("could not find protocol named '%s'\n", protocolName); | |
exit (1); | |
} | |
unsigned int adopteeCount; | |
Protocol * __unsafe_unretained *adoptees = | |
protocol_copyProtocolList (protocol, &adopteeCount); | |
printf ("* Has %d adoptees\n", adopteeCount); | |
for (int i = 0; i < adopteeCount; i++) { | |
printf (" - %s\n", protocol_getName(adoptees[i])); | |
} | |
free (adoptees); | |
unsigned int methodCount; | |
struct objc_method_description *methods; | |
BOOL required, instance; | |
required = YES; | |
instance = YES; | |
methods = | |
protocol_copyMethodDescriptionList (protocol, required, instance, &methodCount); | |
printf ("* Required instance methods (%d)\n", methodCount); | |
DumpMethodDescriptions (methods, methodCount, '-'); | |
free (methods); | |
required = NO; | |
instance = YES; | |
methods = | |
protocol_copyMethodDescriptionList (protocol, required, instance, &methodCount); | |
printf ("* Optional instance methods (%d)\n", methodCount); | |
DumpMethodDescriptions (methods, methodCount, '-'); | |
free (methods); | |
required = YES; | |
instance = NO; | |
methods = | |
protocol_copyMethodDescriptionList (protocol, required, instance, &methodCount); | |
printf ("* Required class methods (%d)\n", methodCount); | |
DumpMethodDescriptions (methods, methodCount, '+'); | |
free (methods); | |
required = NO; | |
instance = NO; | |
methods = | |
protocol_copyMethodDescriptionList (protocol, required, instance, &methodCount); | |
printf ("* Optional class methods (%d)\n", methodCount); | |
DumpMethodDescriptions (methods, methodCount, '+'); | |
free (methods); | |
} // ProtocolInfo | |
static void DumpIvars (void) { | |
XXStuff *stuff = [[XXStuff alloc] init]; | |
stuff.bounds2 = CGRectMake (1, 2, 3, 4); | |
CGRect pointedTo = CGRectMake (10, 20, 30, 40); | |
stuff.boundsPointer = &pointedTo; | |
stuff.blah = @"hello"; | |
stuff.shoeSize = 10; | |
stuff.mikeysPonyFarm = [NSURL URLWithString: @"http://www.fanfiction.net/cartoon/My-Little-Pony/1/0/0/1/0/0/0/0/0/1/0/"]; // kid-rated. gotta keep it clean. | |
stuff.gilliganFactor = 8.2213; | |
stuff.ook = ^{ printf ("hi!\n"); }; | |
stuff.blockHead = ^(NSFileHandle *handle) { printf ("hi 2!\n"); }; | |
// Now dig into the ivars. | |
// Getting objects is easy | |
Ivar ponyIvar = class_getInstanceVariable ([stuff class], "_mikeysPonyFarm"); | |
NSURL *url = object_getIvar (stuff, ponyIvar); | |
printf ("pony farm at %s\n", url.description.UTF8String); | |
// non-object ivars are kind of a pain | |
Ivar gilliganIvar = class_getInstanceVariable ([stuff class], "_gilliganFactor"); | |
ptrdiff_t offset = ivar_getOffset(gilliganIvar); | |
unsigned char *stuffBytes = (unsigned char *)(__bridge void *)stuff; | |
CGFloat gilligans = * ((CGFloat *)(stuffBytes + offset)); | |
printf ("%f gilligans\n", gilligans); | |
} // DumpIvars | |
static void Usage (const char *launchCommand) { | |
printf ("%s operation [optional-arg]\n", launchCommand); | |
printf ("Where operation is one of:\n"); | |
printf (" listclasses -- list all known classes\n"); | |
printf (" listprotocols -- list all known protocols\n"); | |
printf (" dumpivars -- create an object, then introspect its ivars\n"); | |
printf (" classinfo |classname| -- list lots of stuff about a class\n"); | |
printf (" methodinfo |classname| -- list methods for a class\n"); | |
printf (" protocolinfo |protocolname| -- list lots of stuff about a protocol\n"); | |
} // Usage | |
int main (int argc, const char *argv[]) { | |
@autoreleasepool { | |
if (argc == 1) { | |
Usage (argv[0]); | |
return 1; | |
} | |
if (strcmp(argv[1], "listclasses") == 0) { | |
ListClasses (); | |
return 0; | |
} | |
if (strcmp(argv[1], "listprotocols") == 0) { | |
ListProtocols (); | |
return 0; | |
} | |
if (strcmp(argv[1], "classinfo") == 0) { | |
ClassInfo (argv[2]); | |
return 0; | |
} | |
if (strcmp(argv[1], "protocolinfo") == 0) { | |
ProtocolInfo (argv[2]); | |
return 0; | |
} | |
if (strcmp(argv[1], "methodinfo") == 0) { | |
MethodInfo (argv[2]); | |
return 0; | |
} | |
if (strcmp(argv[1], "dumpivars") == 0) { | |
DumpIvars (); | |
return 0; | |
} | |
Usage (argv[0]); | |
return 1; | |
} | |
} // main | |
#if 0 | |
class_getClassVariable | |
http://lists.apple.com/archives/objc-language/2008/Feb/msg00021.html | |
// This returns another kind of encoding string : e.g. Q16@0:8 with sizes | |
// and offsets. Which according to email threads are wrong and should be | |
// ignored | |
// http://lists.apple.com/archives/objc-language/2009/Apr/msg00149.html | |
// const char *typeEncoding = method_getTypeEncoding (*methods); | |
// GC only | |
class_setIvarLayout | |
class_getIvarLayout | |
class_getWeakIvarLayout | |
class_setWeakIvarLayout | |
objc_getFutureClass - used in troll free bridging | |
"Do not call this function yourself" | |
(along with objc_duplicateClass - for KVO) | |
printf ("%s %s %s %s\n", @encode(unsigned char), @encode(unsigned char *), @encode(unsigned char **), @encode (unsigned char ****)); | |
printf ("%s %s %s %s\n", @encode(int), @encode(int *), @encode(int **), @encode(int ****)); | |
// return 0; | |
object_getInstanceVariable() works only with ivars of pointer types. Other types are not supported and may fail, as you noted. | |
object_getInstanceVariable() does not perform the correct ARC memory management for ivars of object types. | |
http://lists.apple.com/archives/objc-language/2013/May/msg00038.html | |
Use object_getIvar instead | |
Expectations vary. For object_getInstanceVariable(), we decided to disallow the unsafe thing in favor of the safer options. You can still get unsafe behavior if you ask for it more directly, such as using class_getIvar() and ivar_getOffset() and casting to void** instead of a pointer-to-object-pointer type. | |
class_createInstance | |
object_getIndexedIvars | |
#endif |
This file contains 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> | |
// Returns an array of names of the types. | |
NSArray *ParseTypeString (NSString *typeString); |
This file contains 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 "typestring.h" | |
static NSString *StructEncoding (char **typeScan); | |
// Remove all numbers from the string. Some type encoding strings include | |
// offsets and/or sizes, and they're often wrong. yay? | |
static NSString *ScrubNumbers (NSString *string) { | |
NSCharacterSet *numbers = [NSCharacterSet decimalDigitCharacterSet]; | |
NSString *numberFree = [[string componentsSeparatedByCharactersInSet: numbers] | |
componentsJoinedByString: @""]; | |
return numberFree; | |
} // ScrubNumbers | |
// Convert simple types. | |
// |typeScan| is scooted over to account for any consumed characters | |
static NSString *SimpleEncoding (char **typeScan) { | |
typedef struct TypeMap { | |
unichar discriminator; | |
char *name; | |
} TypeMap; | |
static TypeMap s_typeMap[] = { | |
{ 'c', "char" }, | |
{ 'i', "int" }, | |
{ 's', "short" }, | |
{ 'l', "long" }, | |
{ 'q', "longlong" }, | |
{ 'C', "unsigned char" }, | |
{ 'I', "unsigned int" }, | |
{ 'S', "unsigned short" }, | |
{ 'L', "unsiged long" }, | |
{ 'Q', "unsigned long long" }, | |
{ 'f', "float" }, | |
{ 'd', "double" }, | |
{ 'B', "bool" }, | |
{ 'v', "void" }, | |
{ '*', "char*" }, | |
{ '#', "class" }, | |
{ ':', "selector" }, | |
{ '?', "unknown" }, | |
}; | |
NSString *result = nil; | |
TypeMap *scan = s_typeMap; | |
TypeMap *stop = scan + sizeof(s_typeMap) / sizeof(*s_typeMap); | |
while (scan < stop) { | |
if (scan->discriminator == **typeScan) { | |
result = @( scan->name ); | |
(*typeScan)++; | |
break; | |
} | |
scan++; | |
} | |
return result; | |
} // SimpleEncoding | |
// Process object/id/block types. Some type strings include the class name in "quotes" | |
// |typeScan| is scooted over to account for any consumed characters. | |
static NSString *ObjectEncoding (char **typeScan) { | |
assert (**typeScan == '@'); | |
(*typeScan)++; // eat the '@' | |
NSString *result = @"id"; | |
if (**typeScan == '"') { | |
(*typeScan)++; // eat the double-quote | |
char *closeQuote = *typeScan; | |
while (*closeQuote && *closeQuote != '"') { | |
closeQuote++; | |
} | |
*closeQuote = '\000'; | |
result = [NSString stringWithUTF8String: *typeScan]; | |
*closeQuote = '\"'; | |
*typeScan = closeQuote; | |
} else if (**typeScan == '?') { | |
result = @"block"; | |
(*typeScan)++; | |
} | |
return result; | |
} // ObjectEncoding | |
// Process pointer types. Recursive since pointers are people too. | |
// |typeScan| is scooted over to account for any consumed characters | |
static NSString *PointerEncoding (char **typeScan) { | |
assert (**typeScan == '^'); | |
(*typeScan)++; // eat the '^' | |
NSString *result = @""; | |
if (**typeScan == '^') { | |
result = PointerEncoding (typeScan); | |
} else if (**typeScan == '{') { | |
result = StructEncoding (typeScan); | |
} else { | |
result = SimpleEncoding (typeScan); | |
} | |
result = [result stringByAppendingString: @"*"]; | |
return result; | |
} // PointerEncoding | |
// Process structure types. Pull out the name of the first structure encountered | |
// and not worry about any embedded structures. | |
// |typeScan| is scooted over to account for any consumed characters | |
static NSString *StructEncoding (char **typeScan) { | |
assert (**typeScan == '{'); | |
(*typeScan)++; // eat the '{' | |
NSString *result = @""; | |
// find the equal sign after the struct name | |
char *equalSign = *typeScan; | |
while (*equalSign && *equalSign != '=') { | |
equalSign++; | |
} | |
*equalSign = '\000'; | |
result = [NSString stringWithUTF8String: *typeScan]; | |
*equalSign = '='; | |
// Eat the rest of the potentially nested structures. | |
int openCount = 1; | |
while (**typeScan && openCount) { | |
if (**typeScan == '{') openCount++; | |
if (**typeScan == '}') openCount--; | |
(*typeScan)++; | |
} | |
return result; | |
} // StructEncoding | |
// Given an Objective-C type encoding string, return an array of human-readable | |
// strings that describe each of the types. | |
NSArray *ParseTypeString (NSString *rawTypeString) { | |
NSString *typeString = ScrubNumbers (rawTypeString); | |
char *base = strdup ([typeString UTF8String]); | |
char *scan = base; | |
NSMutableArray *chunks = [NSMutableArray array]; | |
while (*scan) { | |
NSString *stuff = SimpleEncoding (&scan); | |
if (stuff) { | |
[chunks addObject: stuff]; | |
continue; | |
} | |
if (*scan == '@') { | |
stuff = ObjectEncoding (&scan); | |
[chunks addObject: stuff]; | |
continue; | |
} | |
if (*scan == '^') { | |
stuff = PointerEncoding (&scan); | |
[chunks addObject: stuff]; | |
continue; | |
} | |
if (*scan == '{') { | |
stuff = StructEncoding (&scan); | |
[chunks addObject: stuff]; | |
continue; | |
} | |
// If we hit this, more work needs to be done. | |
stuff = [NSString stringWithFormat: @"(that was unexpected: %c)", *scan]; | |
scan++; | |
} | |
free (base); | |
return chunks; | |
} // ParseTypeString |
This file contains 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 "typestring.h" | |
// clang -g -fobjc-arc -Wall -framework Foundation -o typetest typestring.m typetest.m | |
// Chew through these without crashing. Eyeball the result. | |
static char *g_typestrings[] = { | |
"@16@0:8", | |
"@?16@0:8", | |
"Q16@0:8", | |
"Vv16@0:8", | |
"c16@0:8", | |
"d16@0:8", | |
"q16@0:8", | |
"v16@0:8", | |
"v24@0:8@16", | |
"v24@0:8@?16", | |
"v24@0:8d16", | |
"v24@0:8q16", | |
"v72@0:8@16@24Q32@40@48@56^v64", | |
"@16@0:8", | |
"@88@0:8^*16q24q32q40q48c56c60@64q72q80", | |
"@?16@0:8", | |
"Q16@0:8", | |
"^{CGRect={CGPoint=dd}{CGSize=dd}}16@0:8", | |
"d16@0:8", | |
"v16@0:8", | |
"v24@0:8@16", | |
"v24@0:8@?16", | |
"v24@0:8Q16", | |
"v24@0:8^{CGRect={CGPoint=dd}{CGSize=dd}}16", | |
"v24@0:8d16", | |
"v48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16", | |
"{CGRect={CGPoint=dd}{CGSize=dd}}16@0:8", | |
"@\"<NSObject>\"", | |
"@\"NSURL\"", | |
"@?", | |
}; | |
int main (void) { | |
@autoreleasepool { | |
char **scan = g_typestrings; | |
char **stop = scan + sizeof (g_typestrings) / sizeof (*g_typestrings); | |
while (scan < stop) { | |
printf ("%s\n", *scan); | |
NSArray *jazz = ParseTypeString ( @(*scan) ); | |
NSLog (@"%s -> %@", *scan, jazz); | |
scan++; | |
} | |
} | |
} // main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment