【问题标题】:An id conforming to the protocol vs Qualify an id with a protocol符合协议的 id vs 使用协议限定 id
【发布时间】:2026-02-22 21:00:01
【问题描述】:

我正在浏览 Apple 提供的 Programming with Objective-C 文档。

我正在尝试理解以下段落,但到目前为止,无法理解。

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@required
- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

@interface XYZPieCharView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
// some additional stuff
@end

如果您尝试在 id 上调用 respondsToSelector: 方法 符合上面定义的协议,你会得到一个 编译器错误,它没有已知的实例方法。一旦您 使用协议限定 id,所有静态类型检查都会返回; 如果你尝试调用任何未定义的方法,你会得到一个错误 在指定的协议中。避免编译器错误的一种方法是 设置自定义协议采用 NSObject 协议。

我对“符合协议”和“使用协议限定某些对象”之间的区别感到困惑。如果我们发送一个符合协议的 id - respondsToSelector 消息,为什么编译器会产生错误?

【问题讨论】:

  • 到目前为止,我已经实现了许多协议数据源委托,我无法准确说出这句话想要解释什么。然而,我使用协议没问题。以我个人的拙见,我认为您应该在实际案例中实现它,看看它是如何工作的,并在需要协议时感受它是最快的学习方式。

标签: ios objective-c cocoa methods protocols


【解决方案1】:

如果我们发送一个符合协议的 id - respondsToSelector 消息,为什么编译器会产生错误

是的,这是一件很奇怪的事情。现在,在 ARC 下,如果将声明为 id &lt;XYZPieChartViewDataSource&gt; 的对象发送到 respondsToSelector: 方法、class 方法或任何其他熟悉的基本 NSObject 实例方法,编译器将抛出错误!试试看。

这看起来很奇怪,因为id 应该允许任何(已知)消息发送给它。但是,id &lt;XYZPieChartViewDataSource&gt; 被认为是完全特定的类型;编译器将只允许属于 XYZPieChartViewDataSource 协议的消息。一种现代解决方案是:不要使用

id <XYZPieChartViewDataSource>

改为使用

NSObject<XYZPieChartViewDataSource>*

这导致 NSObject 的所有美味优点都包含在此对象中(就编译器而言),包括响应respondsToSelector:class 的能力以及类似的东西。

这是我在书中插入的一段,讨论这个问题:

奇怪的是,编译器处理类型为id&lt;SomeProtocol&gt; 的对象与处理类型为id 的对象的方式截然不同,只允许将 SomeProtocol 中定义的方法发送到该对象。例如,假设 MyClass 使用类型为 id&lt;MyProtocol&gt;delegate 属性定义。那么如果obj是一个MyClass实例,就谈不上[obj.delegate class];编译器抱怨class 不是已知的实例方法!这是因为obj.delegate 的类型为id&lt;MyProtocol&gt;,因此其唯一 已知的实例方法是doSomething:,MyProtocol 定义的方法。我们可以通过将obj.delegate 转换为id 来解决此问题;当 MyClass 的定义由我们决定时,一个更优雅的解决方案是将delegate 属性声明为NSObject&lt;MyProtocol&gt;* 而不是id&lt;MyProtocol&gt;

【讨论】:

  • 让您的协议扩展&lt; NSObject &gt; 意味着您在运行时获得的对象可能是NSProxy,并且指定协议比指定类类型更具限制性(通常被认为是好的做法)。那么使用协议将类指定为NSObject 有什么好处呢?个人喜好还是我遗漏了什么?
  • @Wain 让协议指定它符合&lt;NSObject&gt; 协议也可以。我从来没有使用过 NSProxy,所以在实践中我从来没有出现过这种区别。另外,我直接从 Apple 的 Greg Parker 那里得到了 NSObject&lt;...&gt;* 的想法,我倾向于按照他所说的去做! :) 我已经修改了我的答案。
  • 当然,这根本不是真正的“好奇”,尽管乍一看语法可能有些混乱。将变量声明为类类型是断言引用对象提供类的方法等,它不断言任何其他内容。将变量声明为协议类型应该与此直接平行(Java 中也是如此)。这里没什么好奇怪的。唯一的问题是语法。由于协议出现在语法中的类之后的 括号中,使用什么来表示“只是协议”,id 是有意义的,因为“任何东西和协议”是一个多余的规范。
  • @CRD 好吧,不同的人对不同的事情感到惊讶。我只能告诉你,我第一次对类型为 id&lt;SomeProtocol&gt; 的引用说 respondsToSelector: 并且编译器向我吐槽时,我感到非常惊讶!
  • @matt - 我们确实是 :-) 但是我建议您面临的问题更多地与默认情况下不包括 NSObject 协议的协议有关,而不是与“协议类型变量”的语法有关。很少有 Obj-C 程序员会错过基于接口的 NSObject(并且在他们这样做时会为错误感到惊讶),但是有多少人会在协议上错过它呢? Java 没有相同的问题,因为只有一个根对象,所以无论如何都必须实现它的方法。当然,在协议上缺少 NSObject 允许协议由具有其他根的类实现...... Obj-C 不是很有趣:)
【解决方案2】:

符合协议

这是当您为您的类定义@interface 时指定该类实现(或符合)协议。这是向编译器提供的信息,用于验证该类确实实现了所需的方法。

@interface MyClass < MyProtocol >

使用协议限定某些对象

这是向指针类型添加额外信息,通常是id,以告诉编译器它引用的对象将实现协议指定的方法(因此它们可以被调用)。与此相反的是,任何未在协议中定义的方法都不能被调用,如果你尝试,你会得到一个警告。

id < MyProtocol > myObject = ...;

【讨论】: