Quirk与Core Data,协议以及readwrite和readonly属性声明 [英] Quirk with Core Data, protocols, and readwrite vs. readonly property declarations

查看:231
本文介绍了Quirk与Core Data,协议以及readwrite和readonly属性声明的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到一个奇怪的问题,涉及Core Data,一个声明的协议,或许LLVM 1.5编译器。这里是情况。



我有一个核心数据模型,其中有两个类,IPContainer和IPEvent,IPContainer是IPEvent的父实体。每个实体在项目中都有一个自定义类,使用mogenerator创建。 mogenerator生成一个额外的子类,只包含建模的属性声明,因此类层次结构实际上是IPEvent> _IPEvent> IPContainer> _IPContainer> NSManagedObject。 IPContainer实体具有名为id的属性,在_IPContainer.h中声明为 @property(nonatomic,retain)NSNumber * id; _IPContainer.m在实现中具有 @dynamic id; ,以告诉Core Data在运行时生成访问器



我也有一个协议IPGridViewGroup在我的项目中声明,它定义了几个属性,其中之一是相同的'id'属性。但是,实现此协议的类不需要setter,因此协议中的属性声明为 @property(readonly)NSNumber * id; IPEvent类声明符合IPGridViewGroup协议。



使用Clang / LLVM 1.0.x编译器(无论Xcode 3.2.2附带的版本),但升级到Xcode 3.2 .3和Clang / LLVM 1.5,一大堆事情改变了。首先,在编译IPEvent类时,我收到以下警告:

  / Volumes / Ratbert / Users / bwebster / Projects / UberProject / iPhotoLibraryManager / IPGridViewGroup.h:19:31:warning:属性'id'需要定义方法'id' - 使用@synthesize,@dynamic或提供方法实现
 属性'id'在类'IPEvent'上标记为只读。无法为其生成setter方法。 

后面紧跟:

   -  [IPEvent setId:]:无法识别的选择器发送到实例0x200483900 

I也尝试重新声明IPEvent类的属性,但是只是给了我一个不同的编译器警告,并在运行时相同的行为:

  /Volumes/Ratbert/Users/bwebster/Projects/UberProject/iPhotoLibraryManager/IPManagedObject/IPEvent.h:14:40:warning:属性'id''retain'属性不匹配继承自'IPGridViewGroup'的属性

现在,这里改变的唯一的事情是编译器,所以改变的催化剂是清楚的,不知道这是否可以被认为是在新版本的编译器中的错误,或者如果旧版本的编译器实际上行为不正确,新版本现在显示它是我自己的代码是buggy。



所以我在这里的问题是:


  1. 似乎应该可以有一个类符合具有readonly属性的协议,但在其自己的实现中为属性提供readwrite访问,是正确的吗?这里的怪癖是readwrite属性实际上是在符合协议的类的超类中声明的。

  2. 我假设控制台消息被打印在肠的某处的核心数据。这是奇怪的,因为IPEvent本身不声明'id'属性显性,除非符合IPGridViewGroup协议。但是,如果是这种情况,那么我会认为编译器错误将会出现,因为它将有效地覆盖一个readwrite属性(在_IPContainer超类中声明的)的readonly版本的相同属性,通常不允许AFAIK。

  3. 如果这是一个编译器错误,那么很好,我可以用几种不同的方式解决它。如果编译器在这里做正确的事情,那么我很遗憾地提出了一种方法来组织所有这些,所以我没有得到任何编译器警告或运行时错误。

编辑,所以,解决方法是在IPEvent类上重新声明属性,但我仍然困惑为什么两个版本的编译器行为不同。还不清楚在协议上声明的属性应该如何与在类上声明的属性进行交互。



如果我在类(而不是协议)中声明了一个readonly属性覆盖了一个readwrite属性,我会收到一条消息warning:attribute'readonly'of property'longitude '限制继承自_IPEvent的属性'readwrite'的属性。似乎如果在协议中声明它具有相同的效果,类似的警告应该从编译器出来。



直觉虽然,我认为,因为IPEvent已经实现这个属性的必要的getter应该算作符合协议,即使它恰巧也实现了属性的setter。

解决方案


现在,改变的唯一的事情是
这里是编译器,所以催化剂
的变化是清楚的,但什么I
不知道这是否可能是
考虑了新版本的
编译器中的错误,或者如果旧版本的
编译器实际上表现不正确的
,新版本现在
显示它是我自己的代码
buggy。


新版本的编译器注意到对同一个类的同一个实例变量的访问器有两个单独的定义。当然,链接器应该抱怨。



旧的编译器应该已经踢了。 @property声明是一个隐式方法声明,无论它出现在类还是协议中。当你有一个类和一个协议定义一个具有相同名称的属性,你最终有两个方法声明一个类的集合。这显然会导致沿线的某处出现问题。



两个编译器之间的差异可能是微不足道的,例如源中的 #import 语句的顺序;甚至修改日期的源文件。



你显然遇到了一个冲突,因为IPContainer类有两个动态方法定义,一个生成一个setter,另一个生成setter和getter。编译器应该如何知道使用哪一个?你刚才告诉它,你想要一个readonly readwrite属性。更糟糕的是,由于这是一个动态属性,没有告诉在运行时实际生成什么。


1似乎应该对
确定类符合协议
具有readonly属性,但是为
中自己的实现提供
读写访问权限,
是否正确?


定义确定。编译器会接受吗?大概。毕竟,在协议中的readonly属性中定义了一个getter方法,但是在类中还定义了一个setter方法。由于协议不限制实现类可以具有的附加方法,可以添加setter方法,就像添加任何其他不相关的方法一样。



但是,这显然是非常非常危险的,尤其是在NSManagedObject子类的情况下。受管对象上下文对它期望在与其协作的类中找到的东西有非常坚定的期望。


2这是奇怪的,因为IPEvent
本身不声明'id'
属性显示,除非
符合IPGridViewGroup
协议。


如果协议要求属性,通过采用协议明确声明它。


3如果这是一个编译器错误,那么
罚分,我可以用一对夫妻
方式现在。如果
编译器在这里做正确的事情
虽然,然后我有一个损失,提出
一种方式组织所有这一切,所以我
不得到任何编译器警告或
运行时错误。


最简单的解决方案是(1) 。这样做违反了有协议的整个目的。 (2)使协议属性readwrite,使编译器和运行时不混淆。


直觉上,我认为
,因为IPEvent已经实现了
必要的getter属性
,应该算作符合
协议,即使它碰巧
也为
属性实现了一个setter。


如果你不使用动态属性,你可能会放弃它。使用动态属性,编译器必须向运行时生成一条消息,解释在运行时生成哪些访问器。在这种情况下应该说什么? 生成一个符合readonly协议的方法,但同时使它同时读写?



难怪编译器抱怨。如果它是一只狗,它会在混乱中润湿自己。



我认为你需要认真重新思考你的设计。从这种非标准,有风险的设计中你能获得什么可能的好处?获取编译器错误是最好的情况。在最坏的情况下,运行时 会与不可预测的结果混淆。



总之,(对莎士比亚表示歉意)...错误不在于编译器,而在于我们自己。


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.

解决方案

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

这篇关于Quirk与Core Data,协议以及readwrite和readonly属性声明的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆