我遇到了一个奇怪的怪癖,涉及核心数据、声明的协议,也许还有 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'
现在,这里唯一改变的是编译器,所以改变的催化剂很清楚,但我不知道这是否可以被认为是新版本编译器的错误,或者如果旧版本的编译器实际上表现不正确,而新版本现在显示这是我自己的代码有问题。
所以我在这里的问题包括:
- 看起来应该可以让一个类符合具有只读属性的协议,但在其自己的实现中为该属性提供读写访问权限,对吗?不过这里的怪癖是 readwrite 属性实际上是在符合协议的类的超类中声明的。
- 我假设控制台消息正在核心数据内部的某个地方打印出来。不过这很奇怪,因为 IPEvent 本身并没有明确声明“id”属性,除非符合 IPGridViewGroup 协议。但是,如果是这种情况,那么我认为会出现编译器错误,因为它会有效地使用相同属性的只读版本覆盖读写属性(在 _IPContainer 超类中声明),AFAIK 通常是不允许的.
- 如果这是一个编译器错误,那很好,我现在可以通过几种不同的方式解决它。如果编译器在这里做正确的事情,那么我将无法想出一种方法来组织所有这些,因此我不会收到任何编译器警告或运行时错误。
编辑:所以,解决方法是在 IPEvent 类上再次重新声明该属性,但我仍然对为什么两个版本的编译器行为不同感到困惑。还不清楚协议上声明的属性应该如何与类上声明的属性进行交互。
如果我在覆盖读写属性的类(而不是协议)中声明只读属性,我会收到消息“警告:属性'经度'的属性'只读'限制从'_IPEvent'继承的属性的属性'读写'”。似乎如果在协议中声明它具有相同的效果,编译器应该会出现类似的警告。
直观地说,我认为由于 IPEvent 已经为该属性实现了必要的 getter,这应该算作“符合协议”,即使它碰巧也为该属性实现了一个 setter。