7

I want to get all the native classes (NSString, NSNumber, int, float, NSSet, NSDictionary) that I have loaded into my iOS Project..

i.e., if I have created a custom class named "TestClass" I don't want it listed...

I have already got a code but it returns names of all classes loaded any way I can modify the code to limit the list to Native classes only?

#import <objc/runtime.h>
#import <dlfcn.h>
#import <mach-o/ldsyms.h>


unsigned int count;
const char **classes;
Dl_info info;

dladdr(&_mh_execute_header, &info);
classes = objc_copyClassNamesForImage(info.dli_fname, &count);

for (int i = 0; i < count; i++) {
  NSLog(@"Class name: %s", classes[i]);
  Class class = NSClassFromString ([NSString stringWithCString:classes[i] encoding:NSUTF8StringEncoding]);
  // Do something with class

}
JAL
  • 41,701
  • 23
  • 172
  • 300
Raon
  • 1,266
  • 3
  • 12
  • 25
  • int, float are not classes. – Martin R Oct 10 '13 at 14:30
  • @MartinR but that will also get into the list in the above case!! – Raon Oct 10 '13 at 14:44
  • If I run your code, I get *only* the classes defined in the app (AppDelegate, ViewController). Do you want all classes that are defined in any framework (Foundation, CoreFoundation, UIKit, WebKit, QuartzCore, ...) or what do you mean by "native classes"? – Martin R Oct 10 '13 at 14:52
  • @MartinR no dude i just meant "above case"-that what i want to do...ie, i want fundamental datatypes also listed.. but the above code will get me classes that are executed (means compiled ,not in packages)... Do you want all classes that are defined in any framework (Foundation, CoreFoundation, UIKit, WebKit, QuartzCore, ...) or what do you mean by "native classes"? EXACTLY :) Can you guess why i need this? – Raon Oct 10 '13 at 15:14
  • Been trying to run this but it won't compile when I try to run a unit test that calls it. Does it work in iOS8.4? Also looking at using it as a replacement for the code below. Do you guys have any idea if there is a performance boost from the above code? – drekka Apr 15 '15 at 14:09

3 Answers3

8

You would get all loaded classes with

int numClasses;
Class * classes = NULL;

classes = NULL;
numClasses = objc_getClassList(NULL, 0);

if (numClasses > 0 )
{
    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);
    for (int i = 0; i < numClasses; i++) {
        Class c = classes[i];
        NSLog(@"%s", class_getName(c));
    }
    free(classes);
}

(Code from objc_getClassList documentation.)

To restrict the list, you can check the bundle from which the class was loaded, e.g.

Class c = classes[i];
NSBundle *b = [NSBundle bundleForClass:c];
if (b != [NSBundle mainBundle])
    ...

for all classes that are not loaded from your application.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Stackoverflow Say to avoid thanks...but i got to say thanks :) – Raon Oct 10 '13 at 15:15
  • is there way to filter out private and public classes among the list that i get over here? – Mehul Thakkar Aug 04 '14 at 09:50
  • Hey Martin, I've attempted a pure Swift solution of your answer [here](http://stackoverflow.com/a/36181261/2415822). Would you mind taking a look and giving me some feedback? Thanks! – JAL Mar 23 '16 at 14:43
6

Here's a pure Swift solution with Swift 3:

var numClasses: Int32 = 0
var allClasses: AutoreleasingUnsafeMutablePointer<AnyClass?>? = nil
defer {
    allClasses = nil
}

numClasses = objc_getClassList(nil, 0)

if numClasses > 0 {
    var ptr = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(numClasses))
    defer {
        ptr.deinitialize()
        ptr.deallocate(capacity: Int(numClasses))
    }
    allClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(ptr)
    numClasses = objc_getClassList(allClasses, numClasses)

    for i in 0 ..< numClasses {
        if let currentClass: AnyClass = allClasses?[Int(i)] {
            print("\(currentClass)")
        }
    }
}

Original solution with Swift 2.2/Xcode 7.3:

var numClasses: Int32 = 0
var allClasses: AutoreleasingUnsafeMutablePointer<AnyClass?> = nil
defer {
    allClasses = nil
}

numClasses = objc_getClassList(nil, 0)

if numClasses > 0 {
    var ptr = UnsafeMutablePointer<AnyClass>.alloc(Int(numClasses))
    defer {
        ptr.destroy()
        ptr.dealloc(Int(numClasses))
        ptr = nil
    }
    allClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>.init(ptr)
    numClasses = objc_getClassList(allClasses, numClasses)

    for i in 0 ..< numClasses {
        if let currentClass: AnyClass = allClasses[Int(i)] {
            print("\(currentClass)")
        }
    }
}

Note that due to the way Swift handles weak pointers (protip: it doesn't), your classes will be overreleased with this code. I've opened SR-1068 about bridging __weak and __unsafe_unretained pointers to Swift. __weak pointers are bridged as UnsafeMutablePointer while __unsafe_unretained pointers are bridged as AutoreleasingUnsafeMutablePointer, which causes the overrelase.

Fortunately, Classes don't do anything on release, so this code is relatively safe, at least for now.

JAL
  • 41,701
  • 23
  • 172
  • 300
  • It should be `0 ..< numClasses`. The allocated memory is not released. Compare https://gist.github.com/bnickel/410a1bdc02f12fbd9b5e. – Martin R Mar 23 '16 at 17:48
  • @MartinR I changed the for loop re your comment. I can't call `dealloc` on the pointer because I'm using an `AutoreleasingUnsafeMutablePointer` rather than an `UnsafeMutablePointer`, so the memory should be deallocated automatically right? – JAL Mar 23 '16 at 17:55
  • I don't think so. Have a look at the linked-to code. – Martin R Mar 23 '16 at 17:58
  • @MartinR I did. I can't call `dealloc`: "error: value of type 'AutoreleasingUnsafeMutablePointer' (aka 'AutoreleasingUnsafeMutablePointer>') has no member 'dealloc'" – JAL Mar 23 '16 at 17:59
  • @MartinR I'm trying to avoid mixing pointer types since `objc_getClassList` requires an `AutoreleasingUnsafeMutablePointer`. But if this is the only way then I'll change my pointers to match the linked code. (re the "Huh? We should have gotten this for free." comment) – JAL Mar 23 '16 at 18:00
  • Yes, that's why that code keep both the UnsafeMutablePointer *and* the AutoreleasingUnsafeMutablePointer. I don't know if there is a more elegant way, never thought about that. – Martin R Mar 23 '16 at 18:01
  • 1
    @MartinR I opened [SR-1068](https://bugs.swift.org/browse/SR-1068) about bridging `__weak` and `__unsafe_unretained` pointers to Swift if you want to follow the discussion with the Apple engineers. – JAL Mar 27 '16 at 00:22
1

iOS info about class

Using Runtime it is possible to:

Objective-C

#import <objc/runtime.h>

- (void) printClassNames {
    int amountClasses = objc_getClassList(NULL, 0);
    printf("Amount of classes: %d", amountClasses);
    
    Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * amountClasses);
    amountClasses = objc_getClassList(classes, amountClasses);
    
    for (int i = 0; i < amountClasses; i++) {
        Class class = classes[i];
        
        if ([NSBundle bundleForClass:class] != [NSBundle mainBundle]) { // restriction that pass classes from main bundle
            continue;
        }
        
        printf("Class name: %s", class_getName(class));
        
        [self printPropertyNamesForClass:class];
        [self printMethodNamesForClass:class];
        
    }
    
    free(classes);
}

- (void) printPropertyNamesForClass:(Class) class {
    uint count;
    objc_property_t* properties = class_copyPropertyList(class, &count);

    for (int i = 0; i < count ; i++) {
        
        const char* propertyName = property_getName(properties[i]);
        printf("\t Property name: %s \n", propertyName);
    }
    free(properties);
}

- (void) printMethodNamesForClass:(Class) class {
    //List of all methods
    unsigned int amountMethod = 0;
    Method *methods = class_copyMethodList(class, &amountMethod);
    
    for (unsigned int i = 0; i < amountMethod; i++) {
        Method method = methods[i];
        
        printf("\t method named:'%s' \n", sel_getName(method_getName(method)));
    }
    
    free(methods);
}

Swift

func printClassNames() {
    
    let amountClasses = objc_getClassList(nil, 0)
    print("Amount of classes: \(amountClasses)")
    
    var classes = [AnyClass](repeating: NSObject.self, count: Int(amountClasses))
    classes.withUnsafeMutableBufferPointer { buffer in
        let autoreleasingPointer = AutoreleasingUnsafeMutablePointer<AnyClass>(buffer.baseAddress)
        objc_getClassList(autoreleasingPointer, amountClasses)
    }
    
    for currentClass in classes {
        
        guard Bundle(for: currentClass) == Bundle.main else {continue}
        print("Class name:\(currentClass)")
        
        printPropertyNamesForClass(currentClass)
        printMethodNamesForClass(currentClass)
    }
    
}

func printPropertyNamesForClass(_ currentClass : AnyClass) {
    var count = UInt32()
    let propertyList = class_copyPropertyList(currentClass, &count)
    let intCount = Int(count)
    
    guard let properties = propertyList, intCount > 0 else {return}
    
    for i in 0 ..< intCount {
        let property : objc_property_t = properties[i]
        
        let nameCString = property_getName(property)
        print("\t Property name:\(String(cString: nameCString))");

    }
    
    free(properties)
}

func printMethodNamesForClass(_ currentClass: AnyClass) {
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(currentClass, &methodCount)
    
    guard let methods = methodList, methodCount > 0 else {return}
    
    var ptr = methods
    for _ in 0 ..< methodCount {
        
        let sel = method_getName(ptr.pointee)
        ptr = ptr.successor()
        
        let nameCString = sel_getName(sel)
        
        print("\t method named:\(String(cString: nameCString))");
    }
    
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205