views:

31

answers:

3

I discovered an odd scenario that produces a compiler warning in XCode that I don't believe is a valid warning.

As an example I created two classes, ClassA & ClassB, that both have an init method called -initWithSomething: One takes an (NSDate *) as the "something" and the other takes (NSString *)

Class A

// ClassA.h
#import <Foundation/Foundation.h>

@interface ClassA : NSObject {
}
-(id)initWithSomething:(NSDate *)something;
@end

// ClassA.m
#import "ClassA.h"

@implementation ClassA
-(id)initWithSomething:(NSDate *)something {
    if (self = [super init]) {
    }
    return self;
}
@end

Class B

// ClassB.h
#import <Foundation/Foundation.h>

@interface ClassB : NSObject {
}
-(id)initWithSomething:(NSString *)something;
@end

// ClassB.m
#import "ClassB.h"

@implementation ClassB
-(id)initWithSomething:(NSString *)something {
    if (self = [super init]) {
    }
    return self;
}
@end

Implementation of another class that uses both ClassA & ClassB

#import "ExampleClass.h"
#import "ClassA.h"
#import "ClassB.h"

@implementation ExampleClass

-(void)doSomething {
    NSDate *date = [NSDate date];
    NSString *string = [NSString stringWithFormat:@"Test"];

    ClassA *classA = [[ClassA alloc] initWithSomething:date];
    ClassB *classB = [[ClassB alloc] initWithSomething:string]; // Produces "Incompatible pointer types sending 'NSString *' to parameter of type 'NSDate *'
    ClassB *classB2 = [[ClassB alloc] initWithSomething:[NSString stringWithFormat:@"Test"]]; // Does NOT produce a warning
    ClassB *classB3 = [[ClassB alloc] initWithSomething:@"Test"]; // Produces the same warning as above.

    [classA release];
    [classB release];
    [classB2 release];
    [classB3 release];
}

Is this a compiler bug? Doesn't seem like either of those lines should produce a warning, especially since the line where "classB2" is initted produces no warnings.
This code actually works fine, the correct class' -initWithSomething: is called and passed the appropriate type of argument.

Clearly, more explicit method names will avoid the issue but I'd like to know why the compiler isn't able to handle this.

Note: I should add that this only seems to occur with -init methods, any other instance or class functions do not seem to produce the warning.

+3  A: 

I think that the problem is that +alloc returns a generic id.

This means that any method can be called on it, and the compiler will see that the first method imported that has the signature -initWithSomething is for class A, which expects an object of type NSDate *.

Also, I do believe that the method +stringWithFormat returns an id which can be compatible with NSDate.

EDIT:

A simple solution to this problem:

@interface ClassA

+(ClassA *) typeSafeAlloc;

// ...
@end

@implementation ClassA

+(ClassA *) typeSafeAlloc
{
    // self is the class variable,  which is the same as:
    // return [ClassA alloc];
    return [self alloc];
}
@end

And repeat the process with ClassB (with typeSafeAlloc returning a ClassB object)

Richard J. Ross III
Cool, thanks for the example.
George
+1  A: 

Look at the return type of +stringWithFormat

Returns a string created by using a given format string as a template into which the remaining argument values are substituted.

+ (id)stringWithFormat:(NSString *)format, ...

(from http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html)

The compiler knows string is an NSString*, same with @"literals". An id however, could be anything (even an NSSDate*).

Logan Capaldo
+1  A: 

alloc return an object of type id and therefore the compiler assumes that initWithSomething belongs to Class A (The first class interface it encounters that has the method name).

Somthing like [(ClassB*)[ClassB alloc] initWithSomething:string]; should solve the problem.