Why does adding a protocol (to id) cause semantic issues? - objective-c

Let's say I have a trivial class like this:
#interface ABPair : NSObject
#property id key;
#property id value;
- (void) printSize;
#end
#implementation ABPair
- (void) printSize {
NSLog(#"the size of your key is: %#", NSStringFromSize([self.key sizeWithAttributes: nil]));
}
#end
This compiles with no warnings (in Xcode 5), and runs successfully, and prints a reasonable value.
However, if I made this one change:
#property id<NSCopying> key;
then I get two compiler errors:
ARC Semantic Issue: No known instance method for selector 'sizeWithAttributes:'
Semantic Issue: Passing 'id' to parameter of incompatible type 'NSSize' (aka 'struct CGSize')
Why is the compiler able to identify the proper method (on NSString) when I provide no information at all about the type, but unable to identify the method when I say that the object must be of a protocol to which that class conforms?

id key declares a "generic" Objective-C variable. It can point to any object,
and the compiler accepts any messages sent to it.
If you compile with ARC, it is only required that the message signature is known
to the compiler at all (from any class).
id<myprotocol> key specifically declares a pointer to an object conforming to that protocol.
The compiler accepts only messages from the <myprotocol> protocol (or other protocols that <myprotocol> inherits from).

Related

KVC on id type conforming to a protocol

I have stumbled upon a problem with key value coding in Xcode 6. It seems as it's impossible to use key value coding on id's conforming to a protocol.
When a property is declared as id the compiler agrees
#property (nonatomic, strong) id foo;
[foo setValue:#"value" forKey:#"key"];
When I set the id to conform to a protocol I get a compiler error. "No known instance method for selector ...".
#property (nonatomic, strong) id<MyProtocol> foo;
[foo setValue:#"value" forKey:#"key"];
It works if I set the type to NSObject, like this:
#property (nonatomic, strong) NSObject<MyProtocol> *foo;
[foo setValue:#"value" forKey:#"key"];
The protocol "MyProtocol" conforms to NSObject, but as far as I understand the object is required to be a subclass of NSObject in order for KVC to work. But why does the first scenario work but not the second?
This isn't new in Xcode 6. (I just tested Xcode 5.1.1 and got an error.) The rules are:
The compiler will let you send any message to a bare id.
The compiler will only let you send a message to id<Protocol1, Protocol2, Protocol3, ...> if the message is defined by one of the named protocols.
The compiler will only let you send a message to SomeClass<Protocol1, Protocol2, Protocol3, ...> if the message is defined by one of the named protocols, or by the class. Messages defined by the class include messages defined by its superclasses and its categories.
The setValue:forKey: method is defined in the NSKeyValueCoding category on the NSObject class. It's not defined on the NSObject protocol.

Why do I have to cast the delegate?

I have a class and this class has a delegate protocol. I create an object in the main class using this class and assigned the main class as the delegate. The main class has a property I would like to read inside the created class. Then I do this:
BOOL prop = [self.delegate myProperty];
Xcode complains that "delegate may not respond to myProperty"
If I am sending a message to self.delegate to read myProperty, and generally Xcode never complains when you send a message to an not casted object, why do I have to do that
BOOL prop = [(myMainClass *)self.delegate myProperty];
to make it work?
To be more clear, here is an example of a message sent to an object without having to cast:
[self.myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop){
int Number = [[obj dictionaryOfNames] count];
}];
self.myArray is an array of objects from another class that are dictionaries and have this property dictionaryOfNames. Xcode never asked me to cast this:
int Number = [[(myOtherClass *)obj dictionaryOfNames] count];
as I had to above for myProperty.
Different classes can conform to a protocol. If you declare that you conform to a protocol you just say that you will implement the requiered methods but you can implement it in a UIView, UIViewController, MyOwnClass, MyOtherClass etc.
Thats why a property is normally declared like this
#property (nonatomic, weak) id <MyProtocol> delegate;
So you just say your delegate is an object which conform to the protocol.
You haven't shown enough code to give a completely definitive answer, but in general terms I would expect that the definition of your delegate is not just id, you've probably used NSObject* or something similar.
The compiler is doing "static" analysis of your source code and trying to determine whether or not the object specified by "self.delegate" might implement that method. If the data type is, say, NSObject*, then the compiler looks through that specific class definition to see if your method is present; if it isn't, then you'll get a warning.
If the data type of the message receiver is id, the compiler tends to give up and say "well, it could be anything so I'll assume this will work".
The result of the expression [obj dictionaryOfNames] is probably of type NSDictionary and the compiler can see that that particular class does respond to the count method.
Note, you can also get this problem if you have specified a class name for a property, but the compiler cannot see the entire class definition from this file. For example, if you have
myobject.h:
#class Something;
#interface MyObject
#property (retain) Something *delegate;
#end
myobject.m:
#import "myobject.h"
[self.delegate doItYouFool];
then the compiler can see that the result of the expression 'self.delegate' is of type Something* but it can not see the actual definition of that class and thus can't look through its supported messages. This usually results in a warning about 'forward definitions'. To fix it, you should import "something.h" into the .m file, so that the compiler has full knowledge about the classes it is working with. To just silence the warning, you cast to id
[(id)self.delegate doItYouFool];
You may, of course, also be getting warnings that 'doItYouFool' isn't a known method, again because you haven't included the header file that defines that message.

type casting self

What does the following casting of self do "ClassA*"? Does this type caste allow you to access ClassA?
ClassA.h:
#interface ClassA: NSObject {
NSUInteger _someNumber;
ClassB *_classB;
}
#property (nonatomic,retain) ClassB *classB;
#property (nonatomic,assign) NSUInteger someNumber;
ClassB Method:
-(void) classBMethod {
[(ClassA*)self setSomeNumber:5];
}
As others have mentioned, if you are dealing with a subclass you should be fine but by the looks of your code I am guessing you would probably be better off with a protocol implementation see http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/_index.html for an overview.
If that isn't a good fit then you may want to look at calling respondsToSelector first to see if the call will work. http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/respondsToSelector:
But overall, you shouldn't be casting self to another class....
A cast tells the compiler that the variable represents a certain type, even though it is not declared as such. It will get rid of compiler warnings, but it does not affect the compiled output. In this case, it is declaring that the instance of ClassB is actually an instance of ClassA so that it can call setSomeNumber:, but this will only work if the object actually implements that method. If the object is not a member of ClassA or a subclass, and its class doesn't implement the method, your program will crash at runtime.
Is ClassB a subclass of ClassA? If it is, no cast should be necessary.
You can cast any pointer to any class you want to disable a warning that it may not respond to the selector, but if it doesn't respond to the selector at runtime, your app will crash when it tries to call that method and the class does not respond.

Xcode Warning: "Property '<x>' and its super class '<y>' don't have matching 'atomic' atrribute"

I'm getting an Xcode warning when compiling several class that subclass existing Cocoa classes. For example, the following class
#interface HMAttitude : CMAttitude
{
double pitch;
double roll;
double yaw;
}
#property (readwrite) double pitch;
#property (readwrite) double roll;
#property (readwrite) double yaw;
#end
-
#implementation HMAttitude
#synthesize pitch, roll, yaw;
- (id) init
{
return [super init];
}
#end
yields three warnings
warning: property 'yaw' and its super class 'CMAttitude' don't have matching 'atomic' attribute
warning: property 'pitch' and its super class 'CMAttitude' don't have matching 'atomic' attribute
warning: property 'roll' and its super class 'CMAttitude' don't have matching 'atomic' attribute
All of the subclasses in question are required in order to create CMMotionManager and CLLocationManager subclasses capable of acting like the superclasses, only loading their data from a csv file. The only reason that I am subclassing them is to gain access (or override) their read-only properties. Without the ability to set these properties, I have no way of returning the same objects as the real CMMotionManager and CLLocationManager classes.
Currently everything works fine aside from having to use a #pragma to ignore the warning which slightly bothers me.
Does anyone know why this warning is being generated? Given the properties aren't being set to nonatomic (atomic is the default), I have no absolutely no clue.
Is there anything that I need to explicitly do in order for these properties to be atomic?
The error message is slightly confusing—if you look at the definition of those properties in the CMAttitude documentation, you'll see that they're actually declared as non-atomic. So, you should declare your properties as non-atomic as well.

Cast an instance of a class to a #protocol in Objective-C

I have an object (a UIViewController) which may or may not conform to a protocol I've defined.
I know I can determine if the object conforms to the protocol, then safely call the method:
if([self.myViewController conformsToProtocol:#protocol(MyProtocol)]) {
[self.myViewController protocolMethod]; // <-- warning here
}
However, XCode shows a warning:
warning 'UIViewController' may not respond to '-protocolMethod'
What's the right way to prevent this warning? I can't seem to cast self.myViewController as a MyProtocol class.
The correct way to do this is to do:
if ([self.myViewController conformsToProtocol:#protocol(MyProtocol)])
{
UIViewController <MyProtocol> *vc = (UIViewController <MyProtocol> *) self.myViewController;
[vc protocolMethod];
}
The UIViewController <MyProtocol> * type-cast translates to "vc is a UIViewController object that conforms to MyProtocol", whereas using id <MyProtocol> translates to "vc is an object of an unknown class that conforms to MyProtocol".
This way the compiler will give you proper type checking on vc - the compiler will only give you a warning if any method that's not declared on either UIViewController or <MyProtocol> is called. id should only be used in the situation if you don't know the class/type of the object being cast.
You can cast it like this:
if([self.myViewController conformsToProtocol:#protocol(MyProtocol)])
{
id<MyProtocol> p = (id<MyProtocol>)self.myViewController;
[p protocolMethod];
}
This threw me for a bit, too. In Objective-C, the protocol isn't the type itself, so you need to specify id (or some other type, such as NSObject) along with the protocol that you want.