#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