什么时候 Objective-C 工厂方法不需要返回类型为 id 的对象



我对Objective-C中工厂方法的返回类型感到困惑。

这篇文章:NSString stringWithFormat 返回类型,为什么是"id"? 应该解决了我的困惑。但是,接受答案的第一行对我来说没有意义。

如果我不能阻止子类化(对吗?(,并且我可以覆盖类方法(即工厂方法(,那么我不必总是返回id吗?

这是一个 API 设计决策。无法阻止子类化,但可以设计 API,例如强烈建议不要这样做。这正是NSNumber的情况.

NSNumber是一个类集群,这意味着它是一个具有多个私有子类的抽象类。此类的用户通常不应该创建自己的子类,因此设计决策是返回 NSNumber * 而不是 id

请注意,这不能用其他类集群(如NSArrayNSString(来完成,因为它们有公共子类。

id用于灵活地进行子类化,因此不使用它是一种阻止客户端对该类进行子类化的方法。

顺便说一句,正如我在这里已经提到的,根据今天,我们有更好的替代方案来替代id返回类型,即 instancetype .您可以在此处阅读有关该主题的更多信息。

考虑两个类,ParentClass 和(其中一个(它的子类ChildClass

ParentClass上比较这些工厂方法。

+(instancetype)parent {
    return [[self alloc] init];
}

现在,由于ChildClass是一个子类,我们只需这样做就可以实例化空ChildClass对象:

ChildClass *child = [ChildClass parent];

我们实际上将返回一个类型 child 的对象。 现在,您可能永远不会使用它,在这种情况下,方法名称使其变得奇怪(但请考虑您可以执行[NSString string]并获得NSString或执行[NSMutableString string]并获得NSMutableString(。


现在你很清楚了:

+(ParentClass*)parent {
    return [[self alloc] init];
}

是有问题的,因为执行[ChildClass parent]将返回一个ParentClass对象。 但是,您的问题是,为什么您不能仅覆盖这些方法(以及其他替代方法(。 你可以,但它很混乱,意味着你正在复制很多不应该复制的代码。

为什么有:

//ParentClass.m
+(ParentClass*)parent {
    return [[self alloc] init];
}

和:

//ChildClass.m
+(ChildClass*)parent {
    return [self child];
}
+(ChildClass*)child {
    return [[self alloc] init];
}

什么时候可以在ParentClass中使用instancetype


覆盖该方法的替代方法是在ChildClass.h中执行此操作:

+(ParentClass*)parent __attribute__((unavailable));

这意味着你根本无法执行[ChildClass parent](你会收到一个警告,你的代码将无法编译(。


您可以执行任一解决方案,但它们都很混乱。 更糟糕的是,如果你有多个子类,它们就会变得很痛苦。

这些是修复没有idinstancetype返回类型的继承工厂方法所必须遵循的长度。 所以,如果你打算对你的类进行子类化......或者,即使你不打算这样做,但希望为自己或您可能分发类的任何其他人保留该选项,您也应该坚持使用工厂方法的instancetype返回类型。

但是如果你想严重阻止子类化,就像苹果出于自己的原因所做的那样 NSNumber ,你可以返回和显式返回类型。 @Gabriele Petronella's在更详细地解释NSNumber的原因方面做得很好。


对于我看到的关于idinstancetype的最佳讨论,我建议查看此链接。 但从本质上讲,instancetype相对于id的优势在于编译器仍将执行某些类型检查。

最新更新