ikevin8me
ikevin8me

Reputation: 4313

How to get the class from bundle

Following the explanation here:

https://github.com/nst/iOS-Runtime-Headers

I am trying to obtain the class of the TUPhoneLogger in the bundle TelephonyUtilities.framework. However, the debugger always show "error: unknown class".

I've 2 different methods:

First method:

NSBundle* b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/TelephonyUtilities.framework"];
BOOL success = [b load];
NSLog(@"%@", [b definedClasses_dd]);

Note: I've created a @interface NSBundle (DDAdditions) extension:

- (NSArray *)definedClasses_dd {
NSMutableArray *array = [NSMutableArray array];    
int numberOfClasses = objc_getClassList(NULL, 0);    
Class *classes = calloc(sizeof(Class), numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for (int i = 0; i < numberOfClasses; ++i) {
    Class c = classes[i];
    if ([NSBundle bundleForClass:c] == self) {
        [array addObject:c];
        const char* nameOfClass = class_getName(c);
        NSString* classString = [NSString stringWithUTF8String:nameOfClass];
        if([classString isEqualToString:@"TUPhoneLogger"]) {
            NSLog(@"Found it! TUPhoneLogger");
            id test= [[c alloc] init];
            NSLog(@"test: %@", test);
        }
    }
}
free(classes);

Second method:

NSBundle* b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/TelephonyUtilities.framework"];
Class telephonyClass = [b classNamed:@"TUPhoneLogger"];
id test= [[telephonyClass alloc] init];
NSLog(@"%@", test);

In the debugger:

enter image description here

Upvotes: 3

Views: 3668

Answers (2)

Nate
Nate

Reputation: 31045

+1 to Victor, as I think it's simpler to just include the Framework as a Build Phase library in your project. Private frameworks are found under the PrivateFrameworks SDK subdirectory, but otherwise, it works similarly as with Public frameworks (with differences described in Victor's answer).

I will just offer one other technique that works, if you do want dynamic loading:

#include <dlfcn.h>
#import <objc/runtime.h>

and then

void* handle = dlopen("/System/Library/PrivateFrameworks/TelephonyUtilities.framework/TelephonyUtilities", RTLD_NOW);
Class c = NSClassFromString(@"TUPhoneLogger");
id instance = [[c alloc] init];
dlclose(handle);

I suppose a benefit of dlopen() is that you don't have to remember to call load, which got you in your second example. A downside is that you should call dlclose() afterwards.

Note: the slight difference in path for dlopen() vs NSBundle bundleWithPath: (file vs. dir)

Note++: this code won't work in the simulator, as the simulator probably is missing that framework (no real phone functionality)


Update

In iOS 9.3, Apple removed Private Frameworks from the SDK. So, since then, it's actually typically the case that you'll need to use this dynamic technique, if the Framework is not one of the public iOS frameworks. See this answer for more

Upvotes: 7

Victor Ronin
Victor Ronin

Reputation: 23298

There is a third method (which I prefer)

a) You link to this framework statically (meaning, you add it to your target)

b) You define necessary class (TUPhoneLogger ) in .h class. You can get it by using class-dump(-z)

c) You include this .h file

d) You just use private class the same way as you use public class.

Small additional explanation

There is no "magic" about private frameworks and private API. The only different that they aren't documented and included in .h files.

Step b) and c) creates .h classes with them and as result they can be used exactly the same way as usual public API.

Upvotes: 2

Related Questions