3

我遇到了一个奇怪的怪癖,涉及核心数据、声明的协议,也许还有 LLVM 1.5 编译器。这是情况。

我有一个核心数据模型,其中有两个类,IPContainer 和 IPEvent,其中 IPContainer 是 IPEvent 的父实体。每个实体在项目中都有一个自定义类,使用 mogenerator 创建。mogenerator 会生成一个仅包含建模属性声明的附加子类,因此类层次结构实际上是 IPEvent > _IPEvent > IPContainer > _IPContainer > NSManagedObject。IPContainer 实体有一个名为“id”的属性,它@property(nonatomic, retain) NSNumber* id;在 _IPContainer.h 中声明。_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'

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

所以我在这里的问题包括:

  1. 看起来应该可以让一个类符合具有只读属性的协议,但在其自己的实现中为该属性提供读写访问权限,对吗?不过这里的怪癖是 readwrite 属性实际上是在符合协议的类的超类中声明的。
  2. 我假设控制台消息正在核心数据内部的某个地方打印出来。不过这很奇怪,因为 IPEvent 本身并没有明确声明“id”属性,除非符合 IPGridViewGroup 协议。但是,如果是这种情况,那么我认为会出现编译器错误,因为它会有效地使用相同属性的只读版本覆盖读写属性(在 _IPContainer 超类中声明),AFAIK 通常是不允许的.
  3. 如果这是一个编译器错误,那很好,我现在可以通过几种不同的方式解决它。如果编译器在这里做正确的事情,那么我将无法想出一种方法来组织所有这些,因此我不会收到任何编译器警告或运行时错误。

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

如果我在覆盖读写属性的类(而不是协议)中声明只读属性,我会收到消息“警告:属性'经度'的属性'只读'限制从'_IPEvent'继承的属性的属性'读写'”。似乎如果在协议中声明它具有相同的效果,编译器应该会出现类似的警告。

直观地说,我认为由于 IPEvent 已经为该属性实现了必要的 getter,这应该算作“符合协议”,即使它碰巧也为该属性实现了一个 setter。

4

3 回答 3

4

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

较新的编译器已经注意到,对于同一个类的同一个实例变量,您有两个不同的访问器定义。当然,链接器应该抱怨。

旧的编译器应该把它踢回来。@property 声明是一个隐式方法声明,无论它出现在类还是协议中。当你让一个类和一个协议都定义了一个同名的属性时,你最终会得到一个类的两组方法声明。这显然会在某个地方引起问题。

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

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

1 让一个类符合具有只读属性的协议似乎应该没问题,但在其自己的实现中为该属性提供读写访问权限,对吗?

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

然而,这显然非常非常危险,尤其是在 NSManagedObject 子类的情况下。托管对象上下文对它期望在它使用的类中找到什么有着非常坚定的期望。

2 这很奇怪,因为 IPEvent 本身并没有明确声明“id”属性,除非符合 IPGridViewGroup 协议。

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

3 如果这是一个编译器错误,那很好,我现在可以通过几种不同的方式解决它。如果编译器在这里做正确的事情,那么我将无法想出一种方法来组织所有这些,因此我不会收到任何编译器警告或运行时错误。

最简单的解决方案是(1)不要定义与类属性重叠的协议。这样做无论如何都违背了拥有协议的全部目的。(2) 使协议属性为读写,这样编译器和运行时就不会混淆。

直观地说,我认为由于 IPEvent 已经为该属性实现了必要的 getter,这应该算作“符合协议”,即使它碰巧也为该属性实现了一个 setter。

如果您不使用动态属性,您可能会侥幸逃脱。使用动态属性,编译器必须向运行时生成一条消息,说明要即时生成哪些访问器。在这种情况下应该说什么?“生成一个符合只读协议的方法,但顺便让它同时读写?”

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

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

简而言之,(向莎士比亚道歉)“……错不在于编译者,而在于我们自己。”

于 2010-06-27T15:11:20.783 回答
0

让我们试着把它分解一下。如果我理解正确:

  • IPEvent是一个继承_IPEvent和实现的类IPGridViewGroup
  • IPGridViewGroupreadonly有财产id。_
  • _IPEvent从继承readwrite属性。id_IPContainer

假设这些假设是正确的(如果我错了,请告诉我)然后IPEvent继承了两个不同id的属性,其中一个是readonly,一个不是。

您是否尝试使用显式修饰符重新定义id属性?IPEventreadwrite

前任:

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

希望编译器能得到提示并生成一个setter。

于 2010-06-24T02:25:44.850 回答
0
@property(readonly) NSNumber* id

看起来不正确。核心数据文档说你应该使用非原子(因为你不能在这里使用线程),你还应该保留 id 因为它是一个对象,而不是分配它(默认)。

如果子类需要访问超类的 ivar,则需要声明其属性并使用 @dynamic 告诉编译器保持安静。看起来你并没有这样做。

它也可能与我发现的这个错误有关,该错误因编译器而异:

http://openradar.appspot.com/8027473如果声明了没有 ivar 的 prop,编译器会忘记超类 ivar 的存在

也可能 id 在 Core Data 中具有特殊含义,您应该使用不同的名称。

于 2010-06-24T02:48:20.993 回答