views:

2359

answers:

3

I have a series of "policy" objects which I thought would be convenient to implement as class methods on a set of policy classes. I have specified a protocol for this, and created classes to conform to (just one shown below)

@protocol Counter   
+(NSInteger) countFor: (Model *)model;
@end

@interface CurrentListCounter : NSObject <Counter> 
+(NSInteger) countFor: (Model *)model;
@end

I then have an array of the classes that conform to this protocol (like CurrentListCounter does)

+(NSArray *) availableCounters {
return [[[NSArray alloc] initWithObjects: [CurrentListCounter class],[AllListsCounter class], nil] autorelease];

}

Notice how I am using the classes like objects (and this might be my problem - in Smalltalk classes are objects like everything else - I'm not sure if they are in Objective-C?)

My exact problem is when I want to call the method when I take one of the policy objects out of the array:

id<Counter> counter = [[MyModel availableCounters] objectAtIndex: self.index];
return [counter countFor: self];

I get a warning on the return statement - it says -countFor: not found in protocol (so its assuming its an instance method where I want to call a class method). However as the objects in my array are instances of class, they are now like instance methods (or conceptually they should be).

Is there a magic way to call class methods? Or is this just a bad idea and I should just create instances of my policy objects (and not use class methods)?

A: 

It turns out it is actually working and the warning is incorrect.

So the question still stands as to whether its a reasonable thing to do (use class methods if they don't need any state)?

And how to best handle the warning (I like to run warning free)?

My only workaround was to have a second protocol (essentially the same as the first but declare it on the instance side):

@protocol CounterInstance
-(NSInteger) countFor: (Model *)model;
@end

And where I access the counters use it instead (to trick the compiler):

id<CounterInstance> counter = [[MyModel availableCounters] objectAtIndex: self.index];
return [counter countFor: self];

Its a bit awkward but does work. Am interested in views from others?

TimM
The warning isn't incorrect. The dynamic nature of Objective-C means that the message is sent to the object anyway. The compiler is raising the warning to let you know that it's really expecting an instance method. As for your solution - I think it's ugly. Just do what Jim said.
Abizern
+4  A: 

A class in Objective-C does work like an instance -- the main underlying difference is that retain counting does nothing on them. However, what you're storing in the -availableCounters array aren't instances (the id type) but classes, which have a type of Class. Therefore, with the -availableCounters definition you specified above, what you need is this:

Class counterClass = [[MyModel availableCounters] objectAtIndex: self.index];
return ( [counterClass countFor: self] );

However, it would probably be semantically better if you used instances rather than classes. In that case, you could do something like the following:

@protocol Counter
- (NSUInteger) countFor: (Model *) model;
@end

@interface CurrentListCounter : NSObject<Counter>
@end

@interface SomeOtherCounter : NSObject<Counter>
@end

Then your model class could implement the following:

static NSArray * globalCounterList = nil;

+ (void) initialize
{
    if ( self != [Model class] )
        return;

    globalCounterList = [[NSArray alloc] initWithObjects: [[[CurrentListCounter alloc] init] autorelease], [[[SomeOtherCounter alloc] init] autorelease], nil];
}

+ (NSArray *) availableCounters
{
    return ( globalCounterList );
}

Then your use of that would be exactly as you specified above:

id<Counter> counter = [[Model availableCounters] objectAtIndex: self.index];
return ( [counter countFor: self] );
Jim Dovey
Thanks Jim - I was interested in seeing how far you can push Objective-C. It sounds like when you hit classes that's where it starts acting a little bit differently (although it does work - classes are still objects, which is nice) - so maybe its better to stick to instances.One thing in your start of the comment - "Class counterClass" is avoiding the protocol, whereas my classes also conform to "Counter". I guess whats missing is being able to properly specify protocols for classes (on the class side) - something like: @interface CurrentListCounter : NSObject <+Counter>
TimM
You might be able to use Class<Counter> counterClass. I'm not sure. But doing so only really limits the messages the compiler will not warn about — ObjC type-checking is really only advisory. And protocols can specify class methods, so you wouldn't need any sort of <+Counter> special syntax (look at the NSObject protocol, for instance).
Jim Dovey
Cool - thats the solution: Class<Counter> counter = [[Model availableCounters]....This is tells the compiler I have an instance of a class that conforms to that protocol. Of course its still a question of taste as to whether using classes as objects is desirable in Obj-c (the consensus seems no)
TimM
+4  A: 

This

id <Counter> counter = [[Model availableCounters] objectAtIndex:0];
return ( [counter countFor: nil] );

Should be

Class <Counter> counter = [[Model availableCounters] objectAtIndex:0];
return ( [counter countFor: nil] );

In the first you have an instance that conforms to . In the second you have a class that conforms to . The compiler warning is correct because instances that conform to don't respond to countFor:, only classes do.

Jon Hess
I also agree that it feels better to use instances than classes here.
Jon Hess
This answer is correct - at first I didn't notice the subtlety of Class<Counter> vs. id<Counter> - which actually answers the original title of my post.
TimM