Littlejon
Littlejon

Reputation: 1066

Objective-C: Get list of subclasses from superclass

In Objective-C is there a way to ask a Class if there are any Subclass implementations.

I have a Base class which has multiple subclasses. I would like to loop through all the subclasses and perform a class selector on each of them.

Edit:

I have a set of classes that can process certain types of data. Each of the processors subclass a base class that provides methods that each processor needs.

Each class knows what data it can process and some classes can process certain types of data better than others.

I would like to have a class method on each class that would provide a response back to a factory class that says yes i can process that data, and give a indication of how well it can process it.

The factory would then make the decision on which class to instantiate based on which class says it can process the data the best.

I have also found this question from 2009 (I did search before I posted this but didn't find anything) Discover subclasses of a given class in Obj-C.

Edit 2:

The + (void)load method looks to be the perfect solution to what I am looking for. So I now have the following:

+ (void)registerSubclass:(Class)subclass {
    NSLog(@"Registered %@", subclass);
}

In my base class the this is my subs.

+(void)load {
    [BaseSwitch registerSubclass:[self class]];
}

This now displays a debug message for each of the subclasses.

My next question is (probably a stupid one), how do I store the classes that get registered in the registerSubclass method. Is there a way to have class variable that I can read later?

Edit 3:

Found some example code here A simple, extensible HTTP server in Cocoa

Which has left me with the following, seems pretty simple after all is said and done. But I thought I would put it here for future reference.

@implementation BaseSwitch

static NSMutableArray *registeredSubclasses;

+ (void)registerSubclass:(Class)subclass {
    if (registeredSubclasses == nil) {
        registeredSubclasses = [[NSMutableArray alloc] init];
    }

    [registeredSubclasses addObject:subclass];

    NSLog(@"Registered %@", subclass);
}

+ (void)logSubclasses {
    for (int i = 0; i < [registeredSubclasses count]; i++) {
        NSLog(@"%@", [registeredSubclasses objectAtIndex:i]);
    }
}

@end

Thanks for everyones suggestions, I will leave the question unanswered for a couple more days incase something else comes up.

Upvotes: 13

Views: 5018

Answers (3)

Philipp Hofmann
Philipp Hofmann

Reputation: 3478

The example from Cocoa with Love can lead to EXC_I386_GPFLT, which stands for General Protection Fault. Instead of the do while loop, we should use a normal while loop to check if the superClass is valid.

#import <objc/runtime.h>

NSArray * ClassGetSubclasses(Class parentClass)
{
    int numClasses = objc_getClassList(NULL, 0);

    // According to the docs of objc_getClassList we should check
    // if numClasses is bigger than 0.
    if (numClasses <= 0) {
        return [NSMutableArray array];
    }

    int memSize = sizeof(Class) * numClasses;
    Class *classes = (__unsafe_unretained Class *)malloc(memSize);

    if (classes == NULL && memSize) {
        return [NSMutableArray array];
    }

    numClasses = objc_getClassList(classes, numClasses);

    NSMutableArray<Class> *result = [NSMutableArray new];

    for (NSInteger i = 0; i < numClasses; i++) {
        Class superClass = classes[i];

        // Don't add the parent class to list of sublcasses
        if (superClass == parentClass) {
            continue;
        }

        // Using a do while loop, like pointed out in Cocoa with Love,
        // can lead to EXC_I386_GPFLT, which stands for General
        // Protection Fault and means we are doing something we
        // shouldn't do. It's safer to use a regular while loop to
        // check if superClass is valid.
        while (superClass && superClass != parentClass) {
            superClass = class_getSuperclass(superClass);
        }

        if (superClass) {
            [result addObject:classes[i]];
        }
    }

    free(classes);

    return result;
}

Check out the following GitHub issues for reference:

Upvotes: 1

ThomasW
ThomasW

Reputation: 17317

This function gives you all subclasses of a class:

#import <objc/runtime.h>

NSArray *ClassGetSubclasses(Class parentClass)
{
  int numClasses = objc_getClassList(NULL, 0);
  Class *classes = NULL;

  classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
  numClasses = objc_getClassList(classes, numClasses);

  NSMutableArray *result = [NSMutableArray array];
  for (NSInteger i = 0; i < numClasses; i++)
  {
    Class superClass = classes[i];
    do
    {
      superClass = class_getSuperclass(superClass);
    } while(superClass && superClass != parentClass);

    if (superClass == nil)
    {
      continue;
    }

    [result addObject:classes[i]];
  }

  free(classes);

  return result;
}

Taken from Cocoa with Love.

Upvotes: 14

Sulthan
Sulthan

Reputation: 130102

You can never list subclasses of a class. In (almost) any programming language. This is one of the basic properties of Object Oriented Programming.

Consider changing your object model.

What you probably want is to create an abstract class and different subclasses but you shouldn't access the subclasses from the abstract class. You should create another object (Factory class) which registers the subclasses and selects the appropiate one when needed.

Note that you cannot efficiently register a class from the class itself. For a class code to be executed, the class has to be loaded first. That means, you have to import its header in some other class and that means that you are actually registering the class by importing its header. There are two possible solutions:

  1. Your factory class has to know the names of all subclasses (either at compile time or reading some configuration file).
  2. Your factory class has a method to which anyone can pass the name of a class to be registered. This is the right solution if you want external libraries to register a new subclass. Then you can put the subclass registration code into the main header of the library.

Upvotes: -8

Related Questions