我遇到了一个奇怪的怪癖,涉及Core Data,一个声明的协议,也许还有LLVM 1.5编译器。这是情况。
我有一个Core Data模型,其中有两个类,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: property 'id' requires method 'id' to be defined - use @synthesize, @dynamic or provide a method implementation
然后,当我实际运行该程序时,它将在控制台中打印出来:
Property 'id' is marked readonly on class 'IPEvent'. Cannot generate a setter method for it.
不久之后:
-[IPEvent setId:]: unrecognized selector sent to instance 0x200483900
我也尝试在IPEvent类上重新声明属性,但这只是给了我一个不同的编译器警告,并且在运行时也有相同的行为:
/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'
现在,这里唯一改变的是编译器,所以改变的催化剂是明确的,但我不知道的是,这是否可以被视为新版本编译器中的错误,或者如果旧版本的编译器实际上表现不正确,新版本现在显示它是我自己的代码,这是错误的。
所以我在这里提出的问题是:
编辑:所以,解决方法是在IPEvent类上重新声明属性,但我仍然感到困惑的是为什么编译器的两个版本的行为不同。还不清楚协议上声明的属性应该如何与类中声明的属性进行交互。
如果我在类中声明了一个readonly属性(而不是协议)来覆盖readwrite属性,我会收到消息“warning:属性'readonly'属性'经度'限制属性'readwrite'属性继承自'_IPEvent “”。看起来如果在协议中声明它具有相同的效果,编译器就会出现类似的警告。
直观地说,我认为既然IPEvent已经为属性实现了必要的getter,那应该算作“符合协议”,即使它恰好也为属性实现了setter。
答案 0 :(得分:4)
现在,唯一改变了的事情 这里是编译器,所以催化剂 对于变化是明确的,但我 不知道这是否可以 被认为是新版本中的一个错误 编译器,或者如果是旧版本的 编译器实际上是在表现 错误,现在是新版本 揭示这是我自己的代码 车。
较新的编译器已经注意到,对于同一个类的同一个实例变量,访问器有两个单独的定义。当然,链接器应该抱怨。
旧的编译器应该把它踢回来。 @property声明是一个隐式方法声明,无论它是出现在类还是协议中。如果同时为类和协议定义具有相同名称的属性,则最终会为一个类生成两组方法声明。这显然会在某个地方引起问题。
两个编译器之间的差异可能是微不足道的,例如源中的#import
语句的顺序,甚至源文件的修改日期。
您显然遇到了冲突,因为IPContainer类有两个动态方法定义,一个只生成一个setter,另一个生成setter和getter。编译器应该如何知道使用哪一个?你刚刚告诉它你想要一个readonly readwrite属性。更糟糕的是,由于这是一个动态属性,因此无法确定在运行时实际生成的内容。
1似乎应该没问题 有一个类符合协议 只有一个属性,但提供 读取属性的访问权限 它自己的实现就是这样 正确的吗?
定义“确定”。编译器会接受吗?大概。毕竟,在协议中的readonly属性中,您已经定义了一个getter方法,但是在类中,您还定义了一个setter方法。由于协议不限制实现类可以具有的其他方法,因此可以添加setter方法,就像添加任何其他不相关的方法一样。
然而,这显然非常非常危险,特别是在NSManagedObject子类的情况下。托管对象上下文对其期望在其使用的类中找到的内容抱有非常坚定的期望。
2这很奇怪,因为IPEvent 本身并没有声明'id' 财产明确,除了 符合IPGridViewGroup 协议
如果协议要求属性,则通过采用协议明确声明它。
3如果这是编译器错误,那么 很好,我可以解决几个问题 现在不同的方式。如果 编译器在这里做正确的事情 但是,我不知所措 有办法组织这一切,所以我 不要收到任何编译器警告或 运行时错误。
最简单的解决方案是(1)不要定义与类属性重叠的协议。这样做无论如何都违背了协议的全部目的。 (2)使协议属性readwrite,以免编译器和运行时混淆。
直观地说,我会这么认为 因为IPEvent已经实现了 必要的物业吸气剂, 应该算作“符合 协议“,即使它恰好发生了 还实现了一个setter 属性。
如果你没有使用动态属性,你可能会逃脱它。使用动态属性,编译器必须向运行时生成一条消息,说明动态生成的访问器。在这种情况下应该说什么? “生成一个符合只读协议的方法,但顺便让它同时读写?”
难怪编译器在抱怨。如果它是一只狗,它会在混乱中弄湿自己。
我认为你需要认真重新思考你的设计。您可以从这种非标准,高风险的设计中获得哪些可能的好处?获取编译器错误是最好的情况。在最坏的情况下, 运行时 会与不可预测的结果混淆。
简而言之,(向莎士比亚道歉)“......错误不在于编纂者,而在于我们自己。”
答案 1 :(得分:0)
让我们尝试将其分解一下。如果我理解正确:
IPEvent
是一个继承_IPEvent
并实施IPGridViewGroup
的类。IPGridViewGroup
有一个readonly
属性id
。_IPEvent
从readwrite
继承id
属性_IPContainer
。假设这些假设是正确的(请告诉我,如果我错了),那么IPEvent
继承了两个不同的id
属性,其中一个是readonly
,其中一个是不
您是否尝试使用明确的id
修饰符重新定义IPEvent
中的readwrite
属性?
例如:
@property (nonatomic, retain, readwrite) NSNumber *id;
希望编译器能获得提示并生成一个setter。
答案 2 :(得分:0)
@property(readonly) NSNumber* id
看起来不正确。核心数据文档说你应该使用非原子(因为你不能在这里使用线程),你也应该保留id,因为它是一个对象,而不是赋值(默认)。
如果子类需要访问超类的ivar,则需要声明其属性并使用@dynamic告诉编译器安静。它看起来不像你那样做。
它也可能与我发现的编译器之间的错误有关:
http://openradar.appspot.com/8027473如果声明没有ivar的道具,编译器会忘记超类ivar
ID也可能在Core Data中具有特殊含义,您应该使用不同的名称。