Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I call +class methods in Objective C without referencing the class?

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)?

like image 324
TimM Avatar asked May 31 '09 12:05

TimM


2 Answers

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 <Counter>. In the second you have a class that conforms to <Counter>. The compiler warning is correct because instances that conform to <Counter> don't respond to countFor:, only classes do.

like image 138
Jon Hess Avatar answered Oct 24 '22 07:10

Jon Hess


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] );
like image 42
Jim Dovey Avatar answered Oct 24 '22 06:10

Jim Dovey