5

通过调用 isKindOfClass: 来检查类的类型是否更合适,或者仅通过 respondsToSelector: 检查它是否支持您正在寻找的方法来采取“鸭子打字”方法?

这是我正在考虑的代码,以两种方式编写:

for (id widget in self.widgets)
{
    [self tryToRefresh:widget];

    // Does this widget have sources? Refresh them, too.
    if ([widget isKindOfClass:[WidgetWithSources class]])
    {
        for (Source* source in [widget sources])
        {
            [self tryToRefresh:source];
        }
    }
}

或者:

for (id widget in self.widgets)
{
    [self tryToRefresh:widget];

    // Does this widget have sources? Refresh them, too.
    if ([widget respondsToSelector:(@selector(sources))])
    {
        for (Source* source in [widget sources])
        {
            [self tryToRefresh:source];
        }
    }
}
4

4 回答 4

6

这取决于实际情况!

我的经验法则是,这只是给我的,还是我将它传递给其他人?

在您的示例中,respondsToSelector:这很好,因为您只需要知道您是否可以向对象发送该消息,因此您可以对结果做一些事情。上课真的没那么重要。

另一方面,如果您打算将该对象传递给其他一些代码,您不一定知道它打算发送什么消息。在这些情况下,您可能会投射对象以便传递它,这可能是您应该isKindOfClass:在投射之前检查它是否真的存在的线索。

要考虑的另一件事是模棱两可。respondsToSelector:告诉您一个对象将响应一条消息,但如果该对象返回的类型与您预期的不同,它可能会产生误报。例如,声明方法的对象:

- (int)sources;

将通过respondsToSelector:测试,但当您尝试在 for-in 循环中使用其返回值时会生成异常。

发生这种情况的可能性有多大?这取决于您的代码、您的项目有多大、有多少人针对您的 API 编写代码等。

于 2012-07-16T18:36:46.430 回答
5

使用目标 C 稍微更惯用respondsToSelector:. Objective C 是高度动态的,因此您在设计时对类结构的假设不一定在运行时成立。respondsToSelector:通过为您提供查询类类型的最常见原因的快捷方式(是否执行某些操作)来解决此问题。

一般而言,如果几个同样吸引人的选择存在歧义,请选择可读性。在这种情况下,这意味着要考虑意图。您是否关心它是否特别是 a WidgetWithSources,或者您真的只关心它是否具有sources选择器?如果是后者,则使用respondsToSelector:. 如果是前者,并且在某些情况下很可能是这样,那么使用isKindOfClass.Readability,在这种情况下,意味着您没有要求读者在类型等价WidgetWithSources和调用sources. respondsToSelector:为读者建立联系,让他们知道您的实际意图。这是对你的程序员同事的小善举。

编辑:@benzado 的回答非常一致。

于 2012-07-16T18:43:00.787 回答
3

@Tim & @benzado 给出了很好的答案,这里是主题的一个变体,之前先介绍了两个案例:

  • 如果在某些时候您可能有对不同类的引用并且需要不同的类,那么这可能是一种情况,isKindOfClass:例如,颜色可能存储在首选项中作为一个NSData序列化NSColor,或者作为具有NSString标准之一的值姓名;NSColor在这种情况下获取isKindOfClass:对象返回的值可能是合适的。
  • 如果您引用了单个类,但随着时间的推移,它的不同版本respondsToSelector:支持不同的方法,那么请考虑例如,许多框架类在更高版本的操作系统中添加了新方法,Apple 的标准建议是使用respondsToSelector:(和不是操作系统版本检查)。
  • 如果您引用了不同的类并且您正在测试它们是否遵守一些非正式协议,那么:

    1. 如果这是您控制的代码,您可以切换到正式协议,然后conformsToProtocol:用作您的测试。这具有测试type而不仅仅是name的优点;否则
    2. 如果这是您无法控制的代码,请使用respondsToSelector:,但我们知道这只是测试是否存在具有相同名称的方法,而不是测试它采用相同类型的参数。
于 2012-07-16T19:44:36.880 回答
2

检查任何一个都可能是一个警告,表明您将要制定一个骇人听闻的解决方案。小部件已经知道他的类和他的选择器。

所以第三种选择可能是考虑重构。将此逻辑移至 a[widget tryToRefresh]可能更简洁,并允许未来的小部件实现额外的幕后逻辑。

于 2012-07-16T19:14:49.580 回答