drekka drekka - 26 days ago 7
iOS Question

iOS: objc_copyClassNamesForImage not working on device

I have a test app (which does nothing because I'm using it to test a bug) that includes 3 frameworks I've built. The frameworks are copied using a copy files phase into the Frameworks directory of the app. I have the following app delegate code:

#import "AppDelegate.h"
@import ObjectiveC;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[NSBundle allFrameworks] enumerateObjectsUsingBlock:^(NSBundle *framework, NSUInteger idx, BOOL *stop) {
unsigned int count = 0;
__unused const char** classes = objc_copyClassNamesForImage([[framework executablePath] UTF8String], &count);
NSRange inFramework = [framework.executablePath rangeOfString:@".app/Frameworks"];
if (inFramework.length > 0) {
NSLog(@"Framework %@, classes: %i", framework.executablePath.lastPathComponent, count);
}
}];
return YES;
}


@end

Which prints out the frameworks from the Frameworks directory and the number of classes contained in each.

When I run this code in the simulator I get the following results:

2016-11-04 12:02:17.682 RuntimeTest[54326:623229] Framework PEGKit, classes: 24
2016-11-04 12:02:17.705 RuntimeTest[54326:623229] Framework Alchemic, classes: 57
2016-11-04 12:02:17.707 RuntimeTest[54326:623229] Framework StoryTeller, classes: 10


However when I run it on a device I get:

2016-11-04 12:07:04.215417 RuntimeTest[1035:365233] Framework PEGKit, classes: 0
2016-11-04 12:07:04.224495 RuntimeTest[1035:365233] Framework Alchemic, classes: 0
2016-11-04 12:07:04.254946 RuntimeTest[1035:365233] Framework StoryTeller, classes: 0


The device is an iPhone 7 with iOS 10.1. I'm pretty sure that this code has worked in the past and it appears that
objc_copyClassNamesForImage
is somehow broken.

My working theory is that this could be some 10.1 bug. Or perhaps something that is set/not set in the build properties.

In anyone able to confirm this? or have any idea what might be wrong?

Answer

Ok I've solved the problem. It appears that on a device the executable paths you get back are not quite correct. I've found that if I create a file reference to the executable, then generate a new URL from the file reference I get a slightly different path. Usually prefixed with /private. Using this new path allows me to interrogate the framework and get the class counts.

FYI, here is the code I'm now using:

-(void) printFrameworkDetails:(CFBundleRef) framework {

    // Get the path to the executable in the framework. Note that on devices, this path is not entirely correct.
    CFURLRef executableURL = CFBundleCopyExecutableURL(framework);
    CFAutorelease(executableURL);

    // Get a file reference from the executable URL, then convert to a system path. This process of going through a file reference corrects the incorrect executable path returned on a device.
    CFURLRef fileRefereceURL = CFURLCreateFileReferenceURL(NULL, executableURL, NULL);
    CFAutorelease(fileRefereceURL);
    CFStringRef executablePathString = CFURLCopyFileSystemPath(fileRefereceURL, kCFURLPOSIXPathStyle);
    CFAutorelease(executablePathString);
    const char *filepath = CFStringGetCStringPtr(executablePathString, kCFStringEncodingUTF8);

    // Now get the number of classes.
    unsigned int ccount = 0;
    const char** classes = objc_copyClassNamesForImage(filepath, &ccount);
    NSLog(@"\n\tframework : %@\n\texecutable: %@\n\tclasses   : %i", framework, executablePathString, ccount);
    free(classes);

}

Comments