NSOrdered​Set

有个问题:为什吗NSOrderedSet不是继承自NSSet的捏?

毕竟 NSOrderedSet是一个 NSSet子类这个逻辑看起来是很完美的。它跟 NSSet 具有相同的方法,还添加了一些 NSArray风格的方法,像 objectAtIndex:。据大家所说,它似乎完全满足了里氏替换原则的要求,其大致含义为:

在一段程序中, 如果 ST的子类型,那么T类型的对象就有可能在没有任何警告的情况下被替换为S类型的对象。

所以,为什吗NSOrderedSetNSObject的一个子类,而不是NSSet甚至是NSArray的子类哪?

可变/不可变的类簇

类簇是 Foundation framework 核心所使用的一种设计模式;也是日常使用 Objective-C 简洁性的本质。

当类簇提供简单的可扩展性的同时,他也把有些事情变得很棘手,尤其是对于一对可变/不可变类像 NSSet / NSMutableSet来说。

正如 Tom Dalling 在这个 Stack Overflow 回答里面巧妙的演示,-mutableCopy 这个方法的创建是跟 Objective-C 对单继承的约束矛盾的。

开始,让我们来看下 -mutableCopy 在类簇中是如果工作的:

NSSet* immutable = [NSSet set];
NSMutableSet* mutable = [immutable mutableCopy];

[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // YES

现在让我们假设下NSOrderedSet事实上是NSSet的子类:

// @interface NSOrderedSet : NSSet

NSOrderedSet* immutable = [NSOrderedSet orderedSet];
NSMutableOrderedSet* mutable = [immutable mutableCopy];

[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // NO (!)
NSSet NSMutableSet NSOrderedSet NSMutableOrderedSet

这样不太好。。。因为这样NSMutableOrderedSet就不能被用来做NSMutableSet类型的方法参数了。那么如果我们让NSMutableOrderedSet作为NSMutableSet的子类会发生什么事情哪?

// @interface NSOrderedSet : NSSet
// @interface NSMutableOrderedSet : NSMutableSet

NSOrderedSet* immutable = [NSOrderedSet orderedSet];
NSMutableOrderedSet* mutable = [immutable mutableCopy];

[mutable isKindOfClass:[NSSet class]]; // YES
[mutable isKindOfClass:[NSMutableSet class]]; // YES
[mutable isKindOfClass:[NSOrderedSet class]]; // NO (!)

这样也许更糟,因为,现在NSMutableOrderedSet就不能被用来做NSOrderedSet类型的方法参数了。

无论怎样去处理它,我们都不能在现有的一对可变/不可变的类上去实现另外一对可变/不可变的类。这在 Objective-C 上是行不通的。

我们可以使用协议来让我们摆脱这个困境(每隔一段时间,多继承的幽灵就会冒出来),而不是自己去承担多继承的风险。 事实上,通过添加以下的协议,Foundation 的集合类可以变的更加面向侧面

  • NSArray : NSObject <NSOrderedCollection>
  • NSSet : NSObject <NSUniqueCollection>
  • NSOrderedSet : NSObject <NSOrderedCollection, NSUniqueCollection>

然而,为了从这种方式中受益,所有现有的 API 都不得不重新调整来接收一个 id<NSOrderedCollectioin> 参数而不是 NSArray。但是这个过度将会是痛苦的,并且可能会打开一整条边界。。。这将意味着它可能永远不会被完全的采纳。。。这将意味着在定义你自己的 API 时,没有动力去采用这种方法。。。这样写也太烦了,因为现在有两种相互不兼容的方式来做事情而不是一个。。这。。。

。。。等等,为什么我们起初要使用 NSOrderedSet 哪?


在 iOS 5 和 OS X Lion 中介绍了 NSOrderedSet。然后,唯一的 API 变化就是在Core Data部分增加了对NSOrderedSet的支持。

这是对使用 Core Data 的人来说极好的消息,因为它解决了一个长期存在的烦恼(没有办法对一个关系集合做任意的排序)。从前,你不得不添加一个位置属性,当每次集合被修改时都要重新计算这个属性。没有一个内置的方法去验证你的位置集合是一个唯一的或者没有间隙的序列。

NSOrderedSet用这种方式对我们的请求做出了回应

不幸的是,对于 API 的设计者来说,在 Foundation 中它的存在创建了一个在诱人的麻烦和误导之间的东西。

尽管它在 Core Data 中有着非常合适的特殊用例,但对于大多数可能使用它的 API 来说,NSOrderedSet也许不是一个很好的选择。在那种仅仅需要一个简单的集合对象作为参数传递的情况下,一个简单的 NSArray 对象就可以了—即使这里有个隐含的意思,里面不应该有重复的元素。当顺序对于一个集合参数很重要时,在这种情况下就使用 NSArray好了(在实现上,应该有代码来处理重复的元素)。如果唯一性很重要,或者对于一个特定的方法来说集合的语义是有意义的,NSSet 仍然是一个很好的选择。


因此,作为一般规则:NSOrderedSet 对于中间和内部表示是有用的,但是你可能不应该把它作为方法参数,除非这是一个特别适用的数据模型。

不说其他的, NSOrderedSet阐释了 Foundation 在使用类簇上的一些迷人影响。在这样做的时候,他可以让我们更好的理解和权衡简单性和可扩展性,有助于帮助我们做自己的应用设计时做出选择。

作者 Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

翻译者
Candyan

Candyan 专业 iOS,业余 Android,偶尔捣鼓下Server的工程师。

下一篇文章

Ruby爱好者总爱嘲笑Objective-C臃肿的语法。尽管新的Object Literals语法让我们瘦了几斤,但那些红头发的恶霸们还总是用他们的单行map和花哨的#to_proc符号嘲讽我们。幸运的是,我们有键-值编码这个王牌。