KVC on id type conforming to a protocol - objective-c

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.

Related

Hiding implementation details with .h and .m in objc

I'm confused about something. If in your .h file you have:
#property (nonatomic, strong, readonly) NSArray *categories;
and then in the .m you have:
#interface MyClass ()
#property (nonatomic, strong, readwrite) NSMutableArray *categories;
#end
If I want to later set categories in the .m file, I can do:
[self setCategories:[NSArray arrayWithArray:categories]];
But then Xcode complains that incompatible pointer types sending NSArray to NSMutableArray. I'm basically trying to hide the implementation details and have the .m use a NSMutableArray and to a consumer
use an NSArray.
[self setCategories:[NSMutableArray arrayWithArray:categories]]; // this gives no Xcode warning
By using the [NSMutableAray arrayWithArray:] method, does it still prevent the consumer of my Class from mutating my categories array?
You've done all you can in objc.
Your internal readwrite declaration needs a mutable array, so Xcode's complaining is right, you have to use a mutable for the setter.
The consumer can do everything with that object, even if it's declared as NSArray you can find out it's a NSMutableArray in reality and change it.
You can't prevent that. But your public declaration shows it should be assumed immutable. There's nothing more you can do.

What does this objective-c property synthesis warning mean?

Since upgrading to Xcode 5.1, I'm starting to see the following warning in some code my project uses. I'm trying to figure out what it means.
Warning: Auto property synthesis will not synthesize property 'responseHeader' because it is 'readwrite' but it will be synthesized 'readonly' via another property
The code where it's occurring, in the .m file:
#interface S3Response ()
#property (nonatomic, readwrite, retain) NSDictionary *responseHeader;
#end
The previous declaration of the property, in the .h file:
#property (nonatomic, readonly) NSDictionary *responseHeader;
There is no #synthesize statement for that property, nor are responseHeader or setResponseHeader defined as methods. There is however an explicit definition of an ivar named responseHeader.
Seems pretty straightforward to me: property is declared as read-only for users of the class, but read-write locally so the class can set it.
What does this warning mean, and what should I do about it?
That code seems to be from the AWS SDK for iOS,
and S3Response is a subclass of AmazonServiceResponse.
The public AmazonServiceResponse interface defines a read-only property
#interface AmazonServiceResponse:NSObject
// ...
#property (nonatomic, readonly) NSDictionary *responseHeader;
#end
which is redefined as read-write in a class extension in the implementation file:
#interface AmazonServiceResponse ()
#property (nonatomic, readwrite, retain) NSDictionary *responseHeader;
#end
Now the subclass S3Response also wants read-write access to this property,
and therefore also defines in the class extension of its implementation file:
#interface S3Response ()
#property (nonatomic, readwrite, retain) NSDictionary *responseHeader;
#end
The compiler complains because – when compiling "S3Response.m" – it does not know
that a setter for the property exists in the superclass (it does not read
the implementation file of the superclass at that point). Also the compiler cannot
simply synthesize a setter in the subclass, because it cannot not know that the
property is backed-up by an instance variable in the superclass.
But you know that a setter will be generated, so you can remove the warning by
adding a #dynamic declaration to the subclass implementation:
#implementation S3Response
#dynamic responseHeader;
...
#dynamic is a "promise" to the compiler that all necessary accessor methods will
be available at runtime.
The problem here is as follows.
By default, if don't write ownership (weak/retain/strong/assign) explicitly, xCode will check the type automatically. So in case of NSDictionary it will be strong. Thus, in interface you will have
#property (nonatomic, readonly, strong) NSDictionary *responseHeader;
Then it will be contradict you private implementation definition
#property (nonatomic, readwrite, retain) NSDictionary *responseHeader;
Compilator doesn't match strong and retain under property synthesizing though it is formally the same thing.
To cure situation you can write retain in both cases, or more correct, you should not write retain at all. It will be strong by default in both definitions.

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

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).

How to create a new instance of Foo class without exposing its constructor in the interface?

This isn't strictly related to NewsstandKit.framework, but there's a live example of this approach in there:
addIssueWithName:date: instance method of NKLibrary creates a newsstand issue — NKIssue — and adds it to the content library and in case of non–failure returns it. At this point NKIssue instance has name and date properties are set to values passed to addIssueWithName:date: initially.
If you check that out, NKIssue class doesn't have a specific initialization method (other than plain init inherited from NSObject) nor #public instance variables (e.g. _name and _date) and its properties are all readonly.
So I don't get how this whole thing could work internally? I would love to mimic this approach in one of my libraries, but can't really figure out how...
Thanks.
Apple's code has access to Apple's code. The readonly properties of NKIssue are likely re-declared as readwrite in a class extension. Have a look at "Property Redeclaration" in TOCPL.
In the header, you see:
#interface Carborundum : NSObject
#property (readonly, copy, nonatomic) NSString * whiskers;
#property (readonly, retain, nonatomic) NSDate * inception;
#end
In the implementation file (or other private file) there's a class extension that has the same properties, with the exact same attributes (this is required) except readonly.
#interface Carborundum ()
#property (copy, nonatomic) NSString * whiskers;
#property (retain, nonatomic) NSDate * inception;
#end
They're synthesized as usual:
#implementation Carborundum
#synthesize whiskers;
#synthesize inception;
#end
This allows code that can see the class extension (i.e., other UIKit code) to use the properties as read-write, while your code, which only has access to the header, is limited to the read-only version. This is enforced by the compiler, not the runtime.
There's a fair number of SO questions that go into this: https://stackoverflow.com/search?q=%5Bobjc%5D+redeclare+property

Objective-C: "warning: property 'owner' type does not match protocol 'PieceModel' property type"

I'm getting this warning. What I'm trying to do is have a family of classes and a parallel family of protocols. The class Piece has a declaration like this:
#interface Piece : NSManagedObject <PieceModel>
{
}
...
#property (nonatomic, retain) Player *owner;
...
#end
PieceModel has this
#protocol PieceModel <NSObject>
...
#property (readonly, nonatomic, retain) id <PlayerModel> owner;
#end
And of course:
#interface Player : NSManagedObject <PlayerModel> { ...
It seems to me this should all be totally safe. Users of the protocols see that something conforming to the PieceModel protocol has an owner that should conform to the PlayerModel protocol. And in fact, every instance of the Piece class returns a Player instance for the owner property, which conforms to the PlayerModel protocol. I do see why there is such a warning. It would not be so safe to try to assign any object that conforms to PlayerModel to owner, since it might not belong to the Player class, but that is not a problem in this case because the property is declared as readonly for the protocol.
Notice I also declared the property as retain, which if I am not mistaken is meaningless for a readonly property, but I also got a different warning about a mismatch between the protocol and the class if I didn't do that. At least the compiler does not complain that one property is readonly and the other is not.
I know I could just declare the class property as returning id <PlayerModel>, but that would be undesirable for a couple reasons. Users of Piece objects that have them statically typed as Pieces would have to do a cast to get something statically typed as a Player. Also, I would have to write the property implementation myself instead of just using #synthesize, or in this case actually #dynamic; Core Data generates the property implementations.
So, can I instruct the compiler to suppress this warning? Or is there a better way to write this code that won't generate the warning?
This generates no warnings ...
#protocol PlayerModel <NSObject>
#end
#protocol PieceModel <NSObject>
- (id<PlayerModel>)owner;
#end
#interface Player : NSObject <PlayerModel> {
}
#end
#interface Piece : NSObject <PieceModel> {
}
#property (nonatomic,retain) Player* owner;
#end
You will then of course not be able to use #synthesize for PieceModel.owner, but that's not so much extra work. Remember that #property declarations are basically just short hand for declaring the setter and getter and defining the behavior of methods generated by #synthesize.
Also keep in mind that dot notation for accessing properties is just syntactic sugar, so if you're fond of dot notation, you'll still be able to use it for accessing 'owner' on variables declared as id<PieceModel>.
Is owner a relationship in your data model? If so, you might find the compiler is confused because NSManagedObject needs to respond to it.
Otherwise, it looks like a limitation of the way properties are handled in subclasses or implementations of protocols. If you replace NSManagedObject by NSObject in Piece and Player and you still get the issue, it might be worth reporting a bug to Apple.
As a work around for the issue, I think you should not declare the property in Piece and declare a separate setter for owner i.e.
#interface Piece : NSManagedObject <PieceModel>
{
}
...
//#property (readonly, nonatomic, retain) id<PlayerModel> owner;
// property declaration not needed because it's in the protocol
-(void) setOwner: (Player*) newOwner;
...
#end
and implement the setter manually.
On an unrelated note, I wouldn't bother declaring properties as nonatomic ever unless I had evidence from a profiler that it provides a significant performance boost.