views:

1415

answers:

4

I am working on an object factory to keep track of a small collection of objects. The objects can be of different types, but they will all respond to createInstance and reset. The objects can not be derived from a common base class because some of them will have to derive from built-in cocoa classes like NSView and NSWindowController.

I would like to be able to create instances of any suitable object by simply passing the desired classname to my factory as follows:

myClass * variable = [factory makeObjectOfClass:myClass];

The makeObjectOfClass: method would look something like this:

- (id)makeObjectOfClass:(CLASSNAME)className
{
    assert([className instancesRespondToSelector:@selector(reset)]);
    id newInstance = [className createInstance];
    [managedObjects addObject:newInstance];
    return newInstance;
}

Is there a way to pass a class name to a method, as I have done with the (CLASSNAME)className argument to makeObjectOfClass: above?

For the sake of completeness, here is why I want to manage all of the objects. I want to be able to reset the complete set of objects in one shot, by calling [factory reset];.

- (void)reset
{
    [managedObjects makeObjectsPerformSelector:@selector(reset)];
}
+3  A: 

It sounds like you want something like:

- (id)makeObjectOfClassNamed:(NSString *)className
{
    Class klass = NSClassFromString(className);
    assert([klass instancesRespondToSelector:@selector(reset)]);
    id newInstance = [klass createInstance];
    [managedObjects addObject:newInstance];
    return newInstance;
}

This would assume a class method named +createInstance. Or you could just use [[klass alloc] init].

To call it:

MyClass *variable = [factory makeObjectOfClassNamed:@"MyClass"];

Depending on what you're trying to do, it might be better to pass around class objects than strings, e.g.:

MyClass *variable = [factory makeObjectOfClass:[MyClass class]];
Michael Tsai
Thank you for your answer. I will use the [MyClass class] approach which you mentioned in your edit.
e.James
+2  A: 

It's pretty easy to dynamically specify a class, in fact you can just reference it by it's name:

id string = [[NSClassFromString(@"NSString") alloc] initWithString:@"Hello!"];
NSLog( @"%@", string );

One other tip, I would avoid using the nomenclature 'managed object' since most other Cocoa programmers will read that as NSManagedObject, from Core Data. You may also find it easier to use a global NSNotification (that all your reset-able objects subscribe to) instead of managing a collection of different types of objects, but you're more informed to make that decision than I am.

Marc Charbonneau
good point about NSManagedObject. I will change my question to avoid the possible confusion.
e.James
+7  A: 

You can convert a string to a class using the function: NSClassFromString

Class classFromString = NSClassFromString(@"MyClass");

In your case though, you'd be better off using the Class objects directly.

MyClass * variable = [factory makeObjectOfClass:[MyClass class]];

- (id)makeObjectOfClass:(Class)aClass
{
    assert([aClass instancesRespondToSelector:@selector(reset)]);
    id newInstance = [aClass createInstance];
    [managedObjects addObject:newInstance];
    return newInstance;
}
Matt Gallagher
The 'Class' type, and [MyClass class] is exactly what I was looking for. Thank you!
e.James
Michael Tsai has pointed out that the [aClass respondsToSelector:@selector(reset)] line should instead read [aClass instancesRespondToSelector:@selector(reset)]. I am making the change in my question, you may wish to do the same in your answer.
e.James
+2  A: 

The bit of the answer missing from the other answers is that you could define a @protocol containing your +createInstance and +reset methods.

Graham Lee
Thank you for the tip! I am planning to use a protocol, but I didn't feel a need to mention it in the question.
e.James