views:

84

answers:

3

I'm running into an odd quirk involving Core Data, a declared protocol, and perhaps the LLVM 1.5 compiler. Here's the situation.

I have a Core Data model that among others has two classes, IPContainer and IPEvent, with IPContainer being the parent entity of IPEvent. Each entity has a custom class in the project for it, created using mogenerator. mogenerator generates an additional subclass that just contains the modeled property declarations, so the class hierarchy is actually IPEvent > _IPEvent > IPContainer > _IPContainer > NSManagedObject. The IPContainer entity has an attribute named 'id', which is declared as @property(nonatomic, retain) NSNumber* id; in _IPContainer.h. _IPContainer.m has @dynamic id; in the implementation, to tell Core Data to generate the accessors at runtime

I also have a protocol IPGridViewGroup declared in my project which defines several properties, one of which is that same 'id' property. However, a setter is not necessary for classes implementing this protocol, so the property in the protocol is declared as @property(readonly) NSNumber* id; The IPEvent class declares that it conforms to the IPGridViewGroup protocol.

This worked fine using the Clang/LLVM 1.0.x compiler (whichever version shipped with Xcode 3.2.2), but upon upgrading to Xcode 3.2.3 and Clang/LLVM 1.5, a whole bunch of things changed. First, I get the following warning when compiling the IPEvent class:

/Volumes/Ratbert/Users/bwebster/Projects/UberProject/iPhotoLibraryManager/IPGridViewGroup.h:19:31: warning: property 'id' requires method 'id' to be defined - use @synthesize, @dynamic or provide a method implementation

Then, when I actually run the program, this gets printed out in the console:

Property 'id' is marked readonly on class 'IPEvent'.  Cannot generate a setter method for it.

Followed shortly by:

-[IPEvent setId:]: unrecognized selector sent to instance 0x200483900

I also tried redeclaring the property on the IPEvent class, but that just gave me a different compiler warning, and the same behavior at runtime:

/Volumes/Ratbert/Users/bwebster/Projects/UberProject/iPhotoLibraryManager/IPManagedObject/IPEvent.h:14:40: warning: property 'id' 'retain' attribute does not match the property inherited from 'IPGridViewGroup'

Now, the only thing that's changed here is the compiler, so the catalyst for the change is clear, but what I don't know is whether this could be considered a bug in the new version of the compiler, or if the old version of the compiler was actually behaving incorrectly, and the new version now reveals that it's my own code that's buggy.

So among the questions I have here are:

  1. It seems like it should be OK to have a class conform to a protocol with a readonly property, but provide readwrite access for the property in its own implementation, is that correct? The quirk here though is that the readwrite property is actually declared in the superclass of the class that conforms to the protocol.
  2. I'm assuming that console message is being printed out somewhere in the bowels of Core Data. This is odd though, because IPEvent itself doesn't declare the 'id' property explicity, except by conforming to the IPGridViewGroup protocol. However, if this is the case, then I would think a compiler error would come up, since it would effectively being overriding a readwrite property (declared in the _IPContainer superclass) with a readonly version of the same property, which AFAIK is normally not allowed.
  3. If this is a compiler bug, then fine, I can work around it in a couple different ways for now. If the compiler is doing the right thing here though, then I'm at a loss to come up with a way to organize all this so I don't get any compiler warnings or runtime errors.

Edit: so, the workaround is to redeclare the property again on the IPEvent class, but I'm still puzzled as to why the two versions of the compiler act differently. It's also unclear how exactly properties declared on a protocol are supposed to interact with those declared on a class.

If I declare a readonly property in the class (rather than the protocol) overriding a readwrite property, I get the message "warning: attribute 'readonly' of property 'longitude' restricts attribute 'readwrite' of property inherited from '_IPEvent'". It seems like if declaring it in the protocol has the same effect, a similar warning should come up from the compiler.

Intuitively though, I would think that since IPEvent already implements the necessary getter for the property, that should count as "conforming to the protocol", even if it happens to also implement a setter for the property.

A: 

Let's try and break this down a bit. If I understand correctly:

  • IPEvent is a class which inherits _IPEvent and implements IPGridViewGroup.
  • IPGridViewGroup has a readonly property id.
  • _IPEvent inherits the readwrite property id from _IPContainer.

Assuming those assumptions are correct (and please tell me if I'm wrong) then IPEvent has inherited two different id properties, one of which is readonly and one of which is not.

Did you try redefining the id property in IPEvent with the explicit readwrite modifier?

ex:

@property (nonatomic, retain, readwrite) NSNumber *id;

Hopefully then the compiler will get the hint and generate a setter.

robinjam
After fiddling a bit more, it looks like the Core Data runtime weirdness goes away when redeclaring the property on IPEvent (as "nonatomic, retain", no explicit readwrite modifier needed), though I still get that compiler warning. This still seems weird though, that the readonly modifier coming from a protocol would override a superclass' readwrite property.
Brian Webster
Provided the code works now, you could suppress the compiler warning by adding `@dynamic id;` to `IPEvent`. And yes, this kind of behaviour is very strange indeed - that's why I try to keep inheritance in Core Data related classes to a minimum.
robinjam
A: 
@property(readonly) NSNumber* id

Looks incorrect. Core Data docs say you should use nonatomic (since you can't use threading here), and you should also be retaining id since it's an object, not assigning it (the default).

If a subclass needs to access an ivar of a superclass it needs to declare its property and use @dynamic to tell the compiler to be quiet. It does not look like you are doing that.

It could also be connected with this bug I found that varies between compilers:

http://openradar.appspot.com/8027473 Compiler forgets superclass ivar exists if a prop without an ivar is declared

It is also possible that id has a special meaning in Core Data and you should use a different name.

Steve Weller
Note that that readonly property is only declared in the IPGridViewGroup protocol, and not directly in any of the Core Data classes. The nonatomic modifier also seems like it would be pretty meaningless for a readonly property, since it only really affects the behavior of synthesized setters, which a readonly property doesn't have.There aren't any ivars involved here, since all of that is managed dynamically by Core Data, so there is no direct ivar access. I think I've run into that ivar bug in other situations though. It also affects some other properties with different names.
Brian Webster
Getters have to lock too. Getting is nowhere near atomic with Core Data, even without considering threads.
Steve Weller
Ah yes, I guess that is indeed true for getters as well.
Brian Webster
+1  A: 

Now, the only thing that's changed here is the compiler, so the catalyst for the change is clear, but what I don't know is whether this could be considered a bug in the new version of the compiler, or if the old version of the compiler was actually behaving incorrectly, and the new version now reveals that it's my own code that's buggy.

The newer compiler has noticed that you have two separate definitions for the accessors for the same instance variable of the same class. Of course, the linker should complain.

The old compiler should have kicked this back. The @property declaration is an implicit method declaration whether it occurs in a class or a protocol. When you have both a class and a protocol define a property with the same name, you end up with two sets of method declarations for one class. This is obviously going to cause problems somewhere along the line.

The differences between the two compilers could be something trivial such as the order of the #import statements in source or even the modification dates on the source files.

You're obviously getting a collision because the IPContainer class has two dynamic method definitions, one generates just a setter and the other a setter and a getter. How should the compiler know which one to use? You've just told it that you want a readonly readwrite property. Worse, since this is a dynamic property, there is no telling what will actually be generated at runtime.

1 It seems like it should be OK to have a class conform to a protocol with a readonly property, but provide readwrite access for the property in its own implementation, is that correct?

Define "OK". Will the compiler accept it? Probably. After all, in a readonly property in the protocol you've defined a getter method but in the class you've also defined a setter method. Since a protocol doesn't restrict what additional methods an implementing class can have, the setter method can be added just like you could add any other unrelated method.

However, this is obviously very, very dangerous, especially in the case of NSManagedObject subclasses. The managed object context has very firm expectations about what it expects to find in the classes it works with.

2 This is odd though, because IPEvent itself doesn't declare the 'id' property explicity, except by conforming to the IPGridViewGroup protocol.

If the property is required by the protocol, it is explicitly declaring it by adopting the protocol.

3 If this is a compiler bug, then fine, I can work around it in a couple different ways for now. If the compiler is doing the right thing here though, then I'm at a loss to come up with a way to organize all this so I don't get any compiler warnings or runtime errors.

The simplest solution is (1) Don't define protocols that overlap class properties. Doing so defeats the entire purpose of having a protocol anyway. (2) Make the protocol property readwrite so the compiler and the runtime are not confused.

Intuitively though, I would think that since IPEvent already implements the necessary getter for the property, that should count as "conforming to the protocol", even if it happens to also implement a setter for the property.

You could probably get away with it if your weren't using dynamic properties. With a dynamic property the complier has to generate a message to the runtime explaining what accessors to generate on the fly. What's it supposed to say in this case? "Generate a method that conforms to the readonly protocol but by the way make it readwrite at the same time?"

No wonder the compiler is complaining. If it was a dog, it would be wetting itself in confusion.

I think you need to seriously rethink your design. What possible benefit can you gain from such a non-standard, risky design? Getting compiler errors is the best case scenario. In the worst case, the runtime gets confused with unpredictable results.

In short, (with apologies to Shakespeare) "...the fault lies not in the complier but with ourselves."

TechZen
I guess part of the confusion comes from how the compiler treats properties declared in protocols somewhat differently from methods. For example, if IPContainer declared: - (NSNumber*)id; - (void)setId:(NSNumber*)newId;And the IPGridViewGroup protocol declared: - (NSNumber*)id;Then there would be no issue with IPEvent declaring that it conforms to IPGridViewGroup. The difference is that the property has metadata that is used by the runtime for things like @dynamic and @synthesize, while no such metadata exists for plain methods.
Brian Webster