代码之家  ›  专栏  ›  技术社区  ›  Brian Webster

核心数据、协议和读写与只读属性声明的比较

  •  3
  • Brian Webster  · 技术社区  · 15 年前

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

    我有一个核心数据模型,其中有两个类IPContainer和IPEvent,IPContainer是IPEvent的父实体。每个实体在项目中都有一个使用mogenerator创建的自定义类。mogenerator生成一个额外的子类,该子类只包含建模的属性声明,因此类层次结构实际上是IPEvent>_i事件>IPContainer>_IPContainer>NSManagedObject。IPContainer实体有一个名为“id”的属性,该属性声明为 @property(nonatomic, retain) NSNumber* id; 在IPContainer.h中_IPContainer.m有 @dynamic id;

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

    这在使用Clang/llvm1.0.x编译器(无论是Xcode3.2.2附带的哪个版本)时运行得很好,但是在升级到Xcode3.2.3和Clang/llvm1.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. 我假设控制台消息是在核心数据的内部某处打印出来的。但这很奇怪,因为IPEvent本身并不明确声明“id”属性,除非符合IPGridViewGroup协议。但是,如果是这样的话,那么我认为编译器 错误 因为它实际上是用同一属性的只读版本覆盖readwrite属性(在IPContainer超类中声明),而AFAIK通常是这样的 允许。
    2. 如果这是一个编译器错误,那么很好,我现在可以用几种不同的方法来解决它。如果编译器在这里做的是正确的,那么我很难想出一种方法来组织所有这些,这样我就不会得到任何编译器警告或运行时错误。

    编辑: 所以,解决方法是在IPEvent类上重新声明属性,但是我仍然不明白为什么编译器的两个版本的行为不同。在协议上声明的属性与在类上声明的属性之间究竟应该如何交互,这一点也不清楚。

    如果我在重写readwrite属性的类(而不是协议)中声明readonly属性,则会收到消息“警告:属性'longitude'的属性'readonly'限制从'\IPEvent'继承的属性的属性'readwrite'”。似乎如果在协议中声明它具有相同的效果,编译器也会发出类似的警告。

    不过,凭直觉,我认为既然IPEvent已经为属性实现了必要的getter,那就应该算作“符合协议”,即使它碰巧也为属性实现了setter。

    3 回复  |  直到 13 年前
        1
  •  4
  •   TechZen    15 年前

    现在,唯一改变的是 这是编译器,所以催化剂 不知道这是否可能 被认为是新版本的 编译器,或者 编译器实际上正在运行 不正确,新版本现在

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

    旧的编译器应该把它赶回去。@property声明是一个隐式方法声明,无论它发生在类还是协议中。当一个类和一个协议都定义了一个同名的属性时,一个类将得到两组方法声明。很明显,这会在这条线路的某个地方引起问题。

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

    1看起来应该可以 使类符合协议 具有只读属性,但提供 它自己的实现,是吗

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

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

    如果该属性是协议所必需的,则通过采用协议来显式声明该属性。

    3如果这是一个编译器错误,那么 好吧,我几分钟就能解决 现在有不同的方法。如果 编译器正在做正确的事情 尽管如此,我还是不知所措 没有任何编译器警告或

    最简单的解决方案是(1)不要定义与类属性重叠的协议。不管怎样,这样做违背了制定协议的全部目的(2) 使协议属性为readwrite,这样编译器和运行时就不会混淆。

    但凭直觉,我认为 因为IPEvent已经实现了 必须的吸气剂, 这应该算作“符合 “协议”,即使发生在 财产。

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

    我认为你需要认真地重新考虑你的设计。你能从这样一个非标准的,冒险的设计中得到什么好处?获取编译器错误是最好的情况。最坏的情况是 运行时 对不可预知的结果感到困惑。

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

        2
  •  0
  •   user269597 user269597    15 年前

    • IPEvent 是一个继承 _IPEvent IPGridViewGroup .
    • IPGridViewGroup组 有一个 readonly 财产 id .
    • _I事件 继承 readwrite 财产 身份证件 _IPContainer .

    I事件 继承了两个不同的 身份证件 只读

    你试过重新定义 中的属性 I事件 与显式 读写

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

        3
  •  0
  •   Steve Weller    15 年前
    @property(readonly) NSNumber* id
    

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

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

    它也可能与我发现的不同编译器的错误有关:

    http://openradar.appspot.com/8027473

    也有可能id在核心数据中有特殊的含义,您应该使用不同的名称。