views:

97

answers:

2

I've come across a strange scenario related to class inheritance in Objective-C.

Let's say i have three classes A, B and C that inherit from a base class X. Classes A, B and X have the constructor:

- (id)InitWithString:(NSString*)someString andDelegate:(id<SomeProtocol>)aDelegate

the only difference being that every class uses a different protocol for the delegate.

What happens is that for A and B the compiler tries to use the method from C. A warning informs me that the protocols required by class C's constructor is not implemented by the provided delegate. There is nothing wrong with the delegate itself, since each class has a delegate that implements the right protocol for the classes' own constructor. Everything works fine at run time and the right functions are called for all classes.

I tried having the constructors return A*, B* or C* instead of the anonymous id, though this still doesn't solve the problem.

The only thing that works is making a cast to the right class like this:

instanceOfA = [(A*)[A alloc] InitWithString:@"" andDelegate:aDelegate];

This seems superfluous and unnecessary. I'm probably missing something obvious here.

+1  A: 

The problem is with the declaration of the method which defines a parameter of a specified type.

You should make the declaration as generic as possible to be valid for all the classes of the object passed as last parameters. if all the protocol inherit from a parent protocol, then you can declare the method as - (id)initWithString:(NSString*)someString andDelegate:(id<ParentProtocol>)aDelegate; differently, you can use the more generic definition - (id)initWithString:(NSString*)someString andDelegate:(id)aDelegate

kiamlaluno
Well in this case I lose the option of enforcing a specific protocol in my initialization. I can't see the logic behind this. Since I am allowed to require a parameter of a specific type NSString* instead of NSObject* or id, why can't I decide to require id<SpecificProtocol>? To clarify, my delegates don't inherit from any other delegates.Also there was an error in the problem statement. My base class X has a different initializer and know nothing about delegates.
MihaiD
Do the used protocols have some methods in common?
kiamlaluno
They have no methods in common
MihaiD
The warning you are seeing is caused by the declaration of the method and the class of the object passed as parameter, which doesn't match the parameter declaration for the method. In this case, you can use a generic type for the parameter, and check at runtime if the parameter type is the one you want to be used; you can use a line like [anotherObject conformsToProtocol:@protocol(SomeProtocol)]. This would mean to not use the static checking, but would avoid the compiler warnings.
kiamlaluno
A: 

I'm not sure how clever the analyzer actually is for these cases and suspect you simply hit one of its limitations.

What you're observing is the compiler seeing the object as id and picking the first method that matches the signature. Try moving the order you including your classes around and you should see that it always picks the selector that gets defined first.

A way to get around this is to initialize the class in two steps:

ClassA *test = [ClassA alloc];
test = [test initWithString:@"" andDelegate:delegate];

In this case, the analyzer knows test is of type ClassA and picks the right selector. It seems that it's not so clever as to tell of what type intermediary objects are that are not assigned to a variable and then just always assumes them to be id.

Adrian
That's absolutely correct. I'm sure the fact that the warning is produced in only 2 of my 3 classes has only to do with include order. Your solution with splitting the initialization also works, though this is still seems excessive.
MihaiD
A class method could work as well, though I didn't test this:`ClassA *test = [[ClassA classAWithString:@"" andDelegate:delegate] retain]`Which has the same result but is only one line long.
Adrian