Wednesday, November 17, 2010

Investigating initialization errors

I spent some time this evening investigating two errors thrown by XCode that baffled me for a bit today.

I wrote some methods like this:

#import "MyClass.h"

@implementation MyClass

- (void)refreshImageView:(UIImageView *)imageView {
   
    CGFloat newHeight = [self modifyHeightFromFrame:imageView.frame];
   
    // ...
}

- (CGFloat)modifyHeightFromFrame:(CGRect)frame {
    float modifier = .10;
    return frame.size.height * modifier;
}

@end

My build failed with the following error: "Incompatible types in initialization" appearing at the line in which I was calling the method. Obviously when assigning a CGFloat to a CGFloat everything looks OK, so why the error?

The problem was I had not yet put the method signature into the header file. I found that because the method returning the CGFloat value was located below the method call, the compiler didn't know about the method or what its return type was. Including a method signature like the one below solved the problem.

/* MyClass.h */

@interface MyClass : NSObject {
    // ...
}

// Method signatures
- (CGFloat)modifyHeightFromFrame:(CGRect)frame;

@end

I wanted to further investigate the reason why you receive this error instead of a nice warning message, because typically in cases like this you will receive a warning like this one

'Class' may not respond to '-method'Name'

However, this case is slightly different (I'll explain why shortly) because the error thrown trumps any warnings and we therefore do not receive that helpful warning. The warning is helpful in two ways: 1) it clues me in to the fact that I have forgotten to add my method signature to the class header file or have forgotten to include the header file altogether and 2) the warning is typically accompanied by a helpful explanation. When viewing the Build Results a little note appears below the warning. If you click 'more' you could see the message explains

Messages without a matching method signature will be assumed to return 'id' and accept '...' as arguments.

What this tells us is that because XCode doesn't recognize the method (due to the missing signature/method declaration), it will assume the return type of the method to be an id (the value of which is a memory address pointing to an object of type void*).

OK fine, so why all the fuss? Well if we take a deeper look into CGFloat we discover that it is really just a typedef for float or double depending on your architecture. And then we find that float/double values (non-integer, primitive data types) can not receive a cast of a pointer value (memory address). Here lies the explanation behind the previously mysterious error. The compiler was trying to initialize a float value with a memory address, and complained with an initially ambiguous error.

Now, however, we are more informed what this error message could mean in the future and will know possibly where to look when we don't receive our nice little warning message.

I might also note that when trying to initialize a structure such as CGRect or CGSize with an undeclared method, you will receive an equivalent error message:

Invalid initializer

Which essentially means "Hey! I can't initialize a structure with only a memory address." And if you try to cast the result instead, you'll receive an error message:

Conversion to non-scalar type requested

All that being said, it all boils down to this: Don't forget to declare your method signatures and include your header files!

No comments:

Post a Comment