Can't figure out 'warning: incompatible Objective-C types' - objective-c

I have a subclass of NSObject that implements an -(id)initWithRootElement:(MyElement *)e method. NSXMLDocument has an identical method that takes an NSXMLElement. When I compile, I get the following warning:
warning: incompatible Objective-C types 'struct MyElement *', expected 'struct NSXMLElement *' when passing argument 1 of 'initWithRootElement:' from distinct Objective-C type
In this case, I'm compiling with Clang + LLVM on SnowLeopard with Xcode 3.2.1, but this also happens with GCC 4.2 on both Leopard and SnowLeopard.
What I don't understand is why it's throwing a warning for my direct NSObject subclass when NSXMLDocument has to inherit from NSXMLNode first? Shouldn't it know that -(id)initWithRootElement:(NSXMLElement *)e only applies to NSXMLDocument which has nothing to do with my class? I could understand if I was trying to overload the method, but I'm not. Please tell me I'm not going crazy...
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSXMLElement.h>
// Importing this here causes the warning...
// #import <Foundation/NSXMLDocument.h>
typedef NSObject MyElement;
#interface TestClass : NSObject
{
}
- (id)initWithRootElement:(MyElement *)element;
#end
#implementation TestClass
- (id)initWithRootElement:(MyElement *)element { return nil; }
#end
// ...but here it doesn't
// #import <Foundation/NSXMLDocument.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// No warning! Inheritance: NSXMLDocument -> NSXMLNode -> NSObject
NSXMLElement *xmlElement = [[NSXMLElement alloc] initWithName:#"foo"];
[[TestClass alloc] initWithRootElement:xmlElement];
// warning: incompatible Objective-C types 'struct MyElement *', expected 'struct NSXMLElement *' when passing argument 1 of 'initWithRootElement:' from distinct Objective-C type
MyElement *element = [[MyElement alloc] init];
[[TestClass alloc] initWithRootElement:element];
[pool drain];
return 0;
}

Objective-C doesn't support covariant declarations.
The declaration of initWithRootElement: in NSXMLDocument is as follows:
- (id)initWithRootElement:(NSXMLElement *)element;
This is different than your declaration:
- (id)initWithRootElement:(MyElement *)element;
In that the argument types are different. This causes confusion in this line of code (where element is of type MyElement *...
[[TestClass alloc] initWithRootElement:element];
... because the return type of +alloc is id and, thus, the compiler doesn't know which method to use; which type of argument is to be expected.
When developing code using Objective-C targeting Apple's frameworks, the rule of thumb is to never have different arguments declared for any given selector.
I'm slightly surprised that the first case doesn't also warn. It likely should. If you have a fairly minimal test case, file a bug via http://bugreport.apple.com/ and append the bug # to this question (or my answer).

Related

Can I use __kindof with protocols in Objective-C?

I know I can make use of Objective-C's lightweight generics using the __kindof keyword, e.g.
NSArray<__kindof BaseClass*> *myArray;
This would remove any warnings for assigning any object from the array to a derived class.
However, instead of BaseClass, I have BaseProtocol, e.g. all my classes in question will conform the BaseProtocol, regardless of their base class. I want to use lightweight generics to dictate "my array consists of elements conforming to BaseProtocol, but they can be any class".
For example in C#, I can say: List<IMyInterface> which would mean that the list consists of elements that implement the IMyInterface interface (I know C# has strong generics and Objective-C has only lightweight generics that doesn't prevent compilation, but you get the idea).
Is there any way to achieve this functionality on Objective-C?
E.g. I want to write
NSArray<__kindof id<MyProtocol>> //compiles, but as the generic argument is "id", it accepts any object, including invalid ones
or
NSArray<id<__kindof MyProtocol>> //doesn't compile
Is this possible?
UPDATE:
Here is a complete self-contained code:
#protocol MyProtocol
#end
#interface MyClass : NSObject<MyProtocol>
#end
#implementation MyClass
#end
#interface AnotherClass : NSObject
#end
#implementation AnotherClass
#end
NSMutableArray<__kindof id<MyProtocol>> *myArray;
void test(){
MyClass *myClassInstance = [[MyClass alloc] init];
AnotherClass *anotherClassInstance = [[AnotherClass alloc] init];
myArray = #[].mutableCopy;
[myArray addObject:myClassInstance];
[myArray addObject:anotherClassInstance]; //i get warning. good.
MyClass *returnedInstance = myArray[0];
AnotherClass *anotherInstance = myArray[1]; //why don't I get a warning here?
}
This syntax is correct:
NSArray <__kindof id <MyProtocol>> *array = ...
You can also omit __kindof, too, and still enjoy lightweight generics. It will still warn you about adding objects of the wrong type even without that keyword. The __kindof is used if you want to pull an object out of that array and assign it to a subtype without a cast, but otherwise __kindof is not needed:
NSArray <id <MyProtocol>> *array = ...
Both of these patterns will warn you if you add an object of a specific type to the array, but that type doesn't conform to MyProtocol.
What this won't warn you about is if you try to add an object of type id by itself. So avoid using unqualified id types within your code and you'll enjoy lightweight generics.
If you're still not seeing the warnings, make sure you have -Wobjc-literal-conversion warning turned on. So, go back to your build settings for the first project and search for "literal" and you'll see the setting (called "Implicit Objective-C Literal Conversions").
Consider this example:
#protocol MyProtocol
#end
#interface Foo: NSObject <MyProtocol>
#end
#interface Bar: Foo
#end
#interface Baz: NSObject
#end
Then consider:
Foo *foo = [[Foo alloc] init];
Bar *bar = [[Bar alloc] init];
Baz *baz = [[Baz alloc] init];
id qux = [[Baz alloc] init];
NSArray <id <MyProtocol>> *array1;
array1 = #[foo, bar, baz, qux]; // warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
Note, that warns us about baz, but not qux. So be wary of using id types.
id <MyProtocol> object1 = array1[0]; // no warning, great
So, that's using the protocol as a lightweight generic and it works as expected.
The only reason you'd add __kindof is if you wanted to avoid this warning:
Foo *foo1 = array1[0]; // warning: initializing 'Foo *__strong' with an expression of incompatible type 'id<MyProtocol> _Nullable'
In that case, you'd use __kindof:
NSArray <__kindof id <MyProtocol>> *array2;
array2 = #[foo, bar, baz]; // again, warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
id <MyProtocol> object2 = array2[0]; // no warning, great
Foo *foo2 = array2[0]; // no warning, great

Using an implementation of a Swift protocol within Obj-C

I'm trying to implement a mediator sort of pattern with a mixture of Swift and Obj-C. The issue I'm facing is with how to deal with using the Swift protocol implementation classes from Obj-C. Check out the code to see what I mean:
The Swift protocol and implementation of it:
#objc public protocol TheProtocol {
func someMethod()
}
#objc public class SwiftClass: NSObject, TheProtocol {
public func someMethod() {
print("someMethod Swift")
}
}
The ObjC-implementation of the protocol:
#import "SwiftAndObjC-Swift.h"
#interface ObjCClass : NSObject <TheProtocol>
- (void) someMethod;
#end
#implementation ObjCClass
- (void) someMethod
{
NSLog(#"someMethod ObjC");
}
#end
My question is how is it possible to define some type in ObjC which is capable of referencing either a SwiftClass or an ObjCClass. For example this does not compile:
#import "SwiftAndObjC-Swift.h"
...
TheProtocol *p = [[ObjCClass alloc] init];
// Error: "Use of undeclared identifier TheProtocol"
This will compile:
#class TheProtocol
TheProtocol *p = [[ObjCClass alloc] init];
But not p can't be used:
#class TheProtocol
TheProtocol *p = [[ObjCClass alloc] init];
[p someMethod];
// Error: Receiver type "The Protocol" is a forward declaration"
(Adding casts to the assignment and/or method invocation doesn't help)
Any solutions?
In Objective-C, a protocol is not a type. You should declare your protocol-conforming class like so:
id<TheProtocol> p = [[ObjCClass alloc] init];
The reason why your forward declaration compiled is because that's what forward declarations do - they announce to the compiler that a class exists and that the linker will fill it in later in the build process.
(That's why changing to id p =... works too.)
In Swift, we declare classes with something like:
class MyClass : Superclass, Protocol, AnotherProtocol { ... }
In Objective-C we use this:
#class MyClass : SuperClass <Protocol, AnotherProtocol>
// ...
#end
See the difference? In Swift, protocols and the superclass are mixed into the inheritance declaration, whereas Objective-C treats them very differently.
Protocols and Classes are treated slightly different across the two languages, thus used differently.
id in ObjectiveC is analogous to AnyObject? in Swift. An object which conforms to SomeProtocol is obviously AnyObject or id in Objective-C.
you don't use #class to declare a protocol... instead you have to import your Swift compatibility header which will contain your protocol definition.
Also to declare an object that conforms to a protocol in Obj C, use id<TheProtocol> as your type
Solved it, I just changed the type to id
id p = [[ObjCClass alloc] init];
[p someMethod];

Property '' not found on object of type 'id'

I'm getting Property 'aVariable' not found on object of type id when trying to read or write aVariable to the array. Shouldn't it be known what class the object is that I added? Also noticed that it works to read the value with NSLog(#" %#",[[anArray objectAtIndex:0] aVariable]);
I'm a beginner at Objective C so it might be some simple thing I'm not getting.
AnObject
#interface AnObject : NSObject
#property (nonatomic,readwrite) int aVariable;
#end
AnotherObject
#interface AnotherObject : NSObject
#end
test.h
#import "test.h"
#implementation AnObject
#synthesize aVariable;
- (id)init
{
self = [super init];
if (self) {
aVariable=0;
}
return self;
}
#end
test.m
#implementation AnotherObject
- (id)init
{
self = [super init];
if (self) { }
return self;
}
- (NSMutableArray*) addToArray
{
NSMutableArray* anArray = [[NSMutableArray alloc] initWithCapacity:0];
AnObject* tempObject = [[AnObject alloc] init];
tempObject.aVariable=10;
[anArray addObject:tempObject];
// Property 'aVariable' not found on object of type 'id'
[anArray objectAtIndex:0].aVariable=[anArray objectAtIndex:0].aVariable + 1;
// Property 'aVariable' not found on object of type 'id'
NSLog(#" %i",[anArray objectAtIndex:0].aVariable);
// This works
NSLog(#" %i",[[anArray objectAtIndex:0] aVariable]);
return anArray;
}
#end
This code:
[anArray objectAtIndex:0].aVariable
Can be broken down into 2 sections:
[anArray objectAtIndex:0]
This returns an id- because you can put any type of object into an array. The compiler doesn't know what type is going to be returned by this method.
.aVariable
This is asking for the property aVariable on the object returned from the array - as stated above, the compiler has no idea what this object is - it certainly won't assume that it is an AnObject, just because that is what you added a line or two earlier. It has to evaluate each statement on its own. The compiler therefore gives you the error.
It is a little more forgiving when using accessor methods:
[[anArray objectAtIndex:0] aVariable];
This will give you a warning (that the object may not respond to the selector) but it will still let you run the code, and luckily enough your object does respond to that selector, so you don't get a crash. However this is not a safe thing to rely on. Compiler warnings are your friends.
If you want to use the dot notation, you need to tell the compiler what type of object is being returned from the array. This is called casting. You can either do this in two steps:
AnObject *returnedObject = [anArray objectAtIndex:0];
int value = returnedObject.aVariable;
Or with a mess of brackets:
int value = ((AnObject*)[anArray objectAtIndex:0]).aVariable;
The extra brackets are required to allow you to use dot notation when casting. If you want to use the accessor methods, you need fewer round brackets but more square brackets:
int value = [(AnObject*)[anArray objectAtIndex:0] aVariable];
-[NSArray objectAtIndex:] returns an id pointer. Since id does not contain information about your protocol the compiler cannot know the object has this property you declared; that is why it complains.
You can solve this by either cast the return value of objectAtIndex: or by using the getter/setter notation, i.e. [anArray objectAtIndex:0] setAVariable:...]. Also make sure you import your protocol definition, otherwise the compiler might also not know about the declared method and issue a warning.

Overriding methods in Objective-C

I am relatively new to Objective-C, and I have been thinking about the fact that all methods are, in effect, virtual. I created this little console program:
#import <Foundation/Foundation.h>
#interface BaseClass : NSObject
{
}
- (void) virtualMethod: (NSInteger) integer;
#end
#interface DerivedClass : BaseClass
{
}
- (void) virtualMethod: (NSString *) string;
#end
#implementation BaseClass
- (void) virtualMethod: (NSInteger) integer
{
NSLog(#"%ld", integer);
}
#end
#implementation DerivedClass
- (void) virtualMethod: (NSString *)string
{
NSLog(#"%#", string); // Program received signal: "EXC_BAD_ACCESS". -- as expected
}
#end
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *arr = [NSArray arrayWithObjects: [BaseClass new], [DerivedClass new], nil];
for (int i = 0; i < arr.count; i++)
{
BaseClass *b = (BaseClass *)[arr objectAtIndex: i];
[b virtualMethod: i];
}
NSLog(#"\n\nTapos na!\n\n");
[pool drain];
return 0;
}
As expected, I got an EXC_BAD_ACCESS in the derived virtualMethod:, since after all, it doesn't take an integer, it takes an NSString * as parameter. The virtual mechanism is based on selectors, and doesn't seem to take signatures into account.
My question: is there something in the language that could prevent such an override with a different signature from happening? Some way to tell the compiler that virtualMethod: should always have the same signature, or to make the compiler issue some kind of hint, warning or error if the signature doesn't match?
I know that a good programmer always indicates the names of types a method should have, but that is a convention, not a language rule. I am asking about a compiler feature to prevent the problem from happening.
Well, there is... but you probably don't want to use it. There's a build setting in Xcode called "Strict Selector Matching" (which passes through to the compiler as -Wstrict-selector-match). This will warn you if the compiler finds two selectors that have different parameter or return types.
Unfortunately, the warning comes up even if the types are different, but compatible. As such, if you turn it on, you'll get a bunch of spurious warnings places that you wouldn't expect to be ambiguous. Feel free to try it out, though.
If you'd like to read more about it, Matt Gallagher wrote up a nice post about it. In the meantime, I'm afraid there's not a great solution here.
The current version of XCode gives you the following warning:
"Conflicting parameter types in implementation of '[method]': '[the type needed]' vs '[the type you wrongly chose]'"

Why am I getting "incompatible pointer type"?

I am trying to create a custom object that simply inherits the NSString class and overrides the 'description' method.
When I compile, however, I am getting a warning:
Incompatible pointer types initializing 'OverrideTester *' with an expression of type 'NSString *'
Here is my code:
main.m
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import "OverrideTester.h"
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *str = #"Programming is fun";
OverrideTester *strOverride = #"Overriding is fun";
NSLog (#"%#", str);
NSLog (#"%#", strOverride);
[pool drain];
return 0;
}
OverrideTester.h
#import <Foundation/Foundation.h>
#interface OverrideTester : NSString
-(void) description;
#end
OverrideTester.m
#import "OverrideTester.h"
#implementation OverrideTester
-(void) description
{
NSLog(#"DESCRIPTION!\n");
}
#end
NSString is part of a class cluster. You cannot just create arbitrary subclasses of it, and when you do, you can't assign constant strings to them (which are type NXConstantString). See Subclassing Notes in the NSString documentation. Generally you don't want to subclass NSString. There are better solutions for most problems.
you are assigning an instance of NSString to your variable of type OverrideTester. If you want an instance of your class, you need to instantiate an instance of that class; type-casting will never change the class of an instance.
description is defined as returning an NSString*:
- (NSString *)description;
Do not try to learn about subclassing and overriding methods by subclassing NSString (or any other class cluster). If you want to play with subclassing and such -- a very good idea when new to the language, assuredly -- then subclass NSObject, potentially multiple levels , and play there.
How do you mean to subclass NSObject,
potentially multiple levels? Isn't it
possible NSObject might have
conflicting methods compared to other
class clusters or just not have them
available to override?
If your goal is to figure out how method overrides work (which I thought it was), then you'd be better off doing it entirely yourself.
I may have mis-read your question.
In any case, subclassing NSString is pretty much never done. There are very very few cases where it is useful. Overriding description in anything but custom classes specifically for debugging purposes is useful, yes. Calling description in production code should never be done.
Also, why would description return an
NSString* in this code?
What would happen if something that expects an NSString* return value were to call your version that doesn't return anything?
A crash.
You are declaring a variable named strOverride of type pointer to OverrideTester. But to that variable, you are trying to assign a pointer to an NSString. You cannot assign a superclass to a variable of a subclass. Imagine a generic class TwoWheeled and a derived class Motorbike. A Motorbike can be treated like a TwoWheeled, but not the other way round as the Motorbike has features a normal TwoWheeled might not have like a motor.