Objective-C,协议和子类化

10

假设我定义了以下协议:

// 用户界面对象的基本协议:

@protocol UIObjectProtocol <NSObject>
@property (assign) BOOL touchable;
@end

// 一个用户界面对象的基本协议,该对象由一个holder对象持有:

@protocol UIHeldObjectProtocol <UIObjectProtocol>
@property (readonly) id holder;
@end

以下是类继承结构:

// User Interface对象的基类,包含touchable属性

@interface UIObject : NSObject <UIObjectProtocol> {
   BOOL _touchable;
}
@end

@implementation UIObject
@synthesize touchable=_touchable;
@end

此时,一切都好。然后我创建了一个名为 UIPlayingCardUIObject 子类。由于其父类也符合 UIObjectProtocol,因此 UIPlayingCard 本身也符合。

现在假设我想让 UIPlayingCard 符合 UIHeldObjectProtocol,所以我进行了以下操作:

@interface UIPlayingCard : UIObject <UIHeldObjectProtocol> {
}
@end

@implementation UIPlayingCard
-(id)holder { return Nil; }
@end
请注意,UIPlayingCard 符合 UIHeldObjectProtocol,而 UIHeldObjectProtocol 间接符合 UIObjectProtocol。然而,在 UIPlayingCard 中我得到了编译器警告,如下所示:

警告:属性“touchable”需要定义方法“-touchable” - 使用 @synthesize、@dynamic 或提供方法实现

这意味着 UIPlayingCard 超类对 UIObjectProtocol 的符合没有被继承(可能是因为在 UIObject 实现范围内声明了 @synthesize 指令)。
我是否必须在 UIPlayingCard 实现中重新声明 @synthesize 指令?
@implementation UIPlayingCard
@synthesize touchable=_touchable; // _touchable now must be a protected attribute
-(id)holder { return Nil; }
@end

是否有其他方法可以消除编译器警告?这是不良设计的结果吗?

提前感谢您的回答。


1
这与你的问题无关,但通常不建议在自己的类/协议/常量/其他内容中使用“Apple”的前缀:你永远不知道大水果公司会在下一个版本中包含什么... - danyowdee
我同意你的看法,danyowdee。事实上,我的应用程序中没有使用“UI”前缀,并且我遵循了你所列出的所有良好实践。在这篇文章中,我只是压制了真正的前缀(例如公司前缀),因为它与问题无关。 - Eduardo Coelho
4个回答

3
你提到了 _touchable 需要被保护,但是你没有包括 @private 编译指令,所以_touchable 已经受到保护
在这种情况下,Clang 在编译时似乎并不会给出这个警告,因此最直接的解决方案是:
  • 将编译器从 GCC 更改为 Clang。
这将带来许多附加好处,包括更加易于理解的错误和警告信息。
GCC 报告这个警告是一个bug。实际上,实现已经被继承,你可以通过一个简单的 main 函数来验证 UIPlayingCardtouchable 属性。所以,你的第二个解决方案是:
  • 忽略这个警告,认为它是错误的。如果你的测试都没有失败,那你也不需要考虑这个问题了。

感谢您提醒关于受保护属性的问题。我宁愿有一个解决这些警告的方法,而不是忽略它们(即使它们是虚假的)。我正在使用XCode 4,但我没有找到直接将编译器从GCC更改为Clang的方法。1)更改编译器会避免此类警告吗?2)您在哪个版本的Xcode中使用Clang编译器?感谢您提供的信息。 - Eduardo Coelho
1
Clang获得了更多的苹果支持,并且具有更现代的架构,因此它往往比其他任何编译器都具有更好的Obj-C支持。我仍在使用Xcode 3系列,但我相信您应该能够在构建方案的一部分中选择编译器。尝试搜索编译器、GCC或“默认编译器”,然后切换该设置。 - Jeremy W. Sherman

2
我需要在UIPlayingCard实现中重新声明@synthesize指令吗?
虽然我从你的示例中没有看出来,但是如果您已经在父类中实现了方法,则不需要重新实现这些方法。
如果编译器警告这些方法不存在,则这是一个错误:
正如Objective C编程语言所述...
当一个类采用一个协议时,它必须实现协议声明的必需方法,如前所述。此外,它必须符合任何采用的协议。如果一个包含协议还包含其他协议,则该类也必须符合它们。一个类可以通过以下任一方式符合一个包含的协议:
- 实现协议声明的方法 - 继承采用协议并实现方法的类。

谢谢分享这个信息。然而,对于最终解决方案我仍然不太清楚。所以,“从采用协议并实现方法的类继承”意味着:即使UIObject类合成了touchable属性,UIPlayingCard子类仍然必须实现-(void)setTouchable:(BOOL)touchable { [super setTouchable:touchable]; }- (BOOL)touchable { return [super touchable]; }来使用超类的实现(而不是重新实现它们),以避免编译器警告?或者还有其他我没有看到的解决方案吗? - Eduardo Coelho
1
每当您创建一个子类时,它将继承其祖先提供的所有功能 - 因此,它会自动符合那些祖先符合的任何协议。这是第二个要点的含义,也是我说无论您使用哪个版本的编译器都是错误的原因:UIObject:NSObject <UIObjectProtocol>表示任何UIObject(包括从中派生的任何类的实例)都将提供协议中定义的方法,并且@synthesize满足此约定。 - danyowdee
1
还有其他的解决方案吗?1. 向苹果提交一个包含示例项目的错误报告。这样,它可能会在未来的版本中得到修复。2. 在他们修复之前,使用Anomie的提示让编译器保持安静;-) - danyowdee

2

我再提供一种消除警告的方法:使用@dynamic,编译器会认为实现将以某种其他方式提供,而不是在类的实现中声明(在这种情况下,它由超类提供)。


总结一下:解决方法(您认为这是解决方法还是在这种情况下使用@dynamic语言特性是正确的?)如下所示:1)在UIObject实现中:@synthesize touchable=_touchable 2)在UIPlayingCard实现中:@dynamic touchable;。这是我可以做的最小实现来解决这个问题,对吗? - Eduardo Coelho
我还没有查看语言定义以确定GCC或CLANG的行为是否正确(或者这种情况是否得到解决或不清楚)。如果GCC是正确的,或者它没有得到解决或不清楚,我会称其为正常和适当的使用;如果CLANG是正确的,我会称其为一种解决方法。无论哪种方式,这就是我会做的。是的,这是最小的实现来消除警告。 - Anomie

0
一个协议就像是一份合同。你的意思是UIPlayingCard必须实现holder和touchable。
当编译器编译UIPlaying card时,它没有看到任何地方定义了@property (nonatomic) BOOL touchable;。
你不能给playingCard添加synthesize,因为playingCard没有touchable ivar。
更改:
@protocol UIHeldObjectProtocol <UIObjectProtocol>

@protocol UIHeldObjectProtocol <NSObject>

由于UIPlayingCard继承自UIObject,因此协议不需要相互继承。


我已经验证了,使用GCC,在UIObject.h中添加@property声明并不会阻止GCC发出警告。 - Jeremy W. Sherman
是的,我的错误...我会修正答案。 - amattn
那是错误的:属性是在 UIObject 遵循的协议中定义的。由于 UIPlayingCard 继承了 UIObject,它自动符合 UIObjectProtocol - danyowdee
是的,这就是 UIPlayingCard 继承自 UIObject 的意义,因此自动符合 UIObjectProtocol。正如 Jeremy W. Sherman 所说:“实现实际上是继承的,你可以通过一个简单的主函数来验证 UIPlayingCard 的可触摸属性。”。问题在于编译器显示了不愉快的警告。 - Eduardo Coelho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接