【发布时间】:2014-11-25 20:17:06
【问题描述】:
作为一个只学习 Swift 做事方式的学科,我尽量避免对 NSWhatevers 进行类型转换,但是,有时这样做会非常令人沮丧。
我有一个表格视图控制器,带有可排序的数据列标题。我使用的数据是一个对象数组。我想按这些对象中的字段名称对数组进行排序。如果用户再次点击相同的列标题按钮,排序顺序会颠倒,这意味着我必须跟踪每个字段/列的当前排序方向。
在 Objective-C/NSArray API 中,动态执行此操作非常简单。您只需在NSArray 上使用NSSortDescriptor 和sortedArrayUsingDescriptors,将字段/列名称和排序顺序传递给它,您就可以得到所需的内容。
然而,在 Swift 中,您不能将字段名称作为字符串动态传递。相反,您必须明确指定要使用的字段,这意味着您必须设置详细开关或 if/else 构造,或者您必须定义一个字典,将排序字段作为键,闭包作为值。然后闭包将传递给您的排序函数。我在操场上设置它只是为了看看我能用字典/闭包查找路线找出什么。这是我想出的使用一些虚拟对象的方法:
class Order {
var orderNumber:String
var invoiceNumber:String
var orderDate:NSDate
init(orderNumber:String, invoiceNumber:String, orderDate:NSDate) {
self.orderNumber = orderNumber
self.invoiceNumber = invoiceNumber
self.orderDate = orderDate
}
}
我在这样的数组中实例化其中的一些:
var orders = [Order(orderNumber: "123456", invoiceNumber: "2342342", orderDate: NSDate()),
Order(orderNumber: "332422", invoiceNumber: "848484", orderDate: NSDate()),
Order(orderNumber: "423425", invoiceNumber: "488244", orderDate: NSDate())]
然后我创建我的查找字典,它提供了我想要排序的闭包:
var sorters = [
"orderNumber" :
[true : { (o1: Order, o2: Order) -> Bool in return o1.orderNumber < o2.orderNumber },
false : { (o1: Order, o2: Order) -> Bool in return o1.orderNumber > o2.orderNumber }],
"invoiceNumber" :
[true : { (o1: Order, o2: Order) -> Bool in return o1.invoiceNumber < o2.invoiceNumber },
false : { (o1: Order, o2: Order) -> Bool in return o1.invoiceNumber > o2.invoiceNumber }],
"orderDate" :
[true : { (o1: Order, o2: Order) -> Bool in return o1.orderDate < o2.orderDate },
false : { (o1: Order, o2: Order) -> Bool in return o1.orderDate > o2.orderDate }]
]
您可能想知道true 和false 键的作用。这是为了确定是升序(true)还是降序(false)排序。当我实际运行排序时,您可以看到这是如何进行的:
var currentSortField = "invoiceNumber"
var ascending = false
orders.sort(sorters[currentSortField]![ascending]!)
注意:我还必须为 NSDate 重载大于和小于运算符,才能在“orderDate”字段中进行比较:
func >(lhs:NSDate, rhs:NSDate) -> Bool {
return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
}
func <(lhs:NSDate, rhs:NSDate) -> Bool {
return lhs.compare(rhs) == NSComparisonResult.OrderedDescending
}
我意识到这个问题的背景有点长,但是,我只是有点困惑,Swift 似乎没有更优雅的解决方案来解决这个问题,并希望提供我来的完整解决方案与时俱进。请记住,在这种情况下,等效的 NSWhatever 是一个单行:
var sorted = (orders as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: currentSortField, ascending: ascending)])
那么问题来了。在 Swift 中是否有更简洁、更具表现力和惯用的方式来实现这一点,而无需借助 Foundation API?
如果您想试用我创建的用于测试的 Playground,可以在此处获取:https://www.dropbox.com/s/8m53bprzui50muq/Sorting.playground.zip?dl=0
更新 在与 cmets 中的 Matt 讨论之后,我继续实施了一个排序闭包,在使我的 Order 对象继承自 NSObject 之后,使用valueForKey: 进行了妥协。这是一个折衷方案,但它使我无法完全放弃 Swift 排序功能,而且我不必输入一大堆代码。它是这样的:
var currentSortField = "invoiceNumber"
var ascending = false
var sorter = { (o1: Order, o2: Order) -> Bool in
if ascending {
return (o1.valueForKey(currentSortField) as String) < (o2.valueForKey(currentSortField) as String)
} else {
return (o1.valueForKey(currentSortField) as String) > (o2.valueForKey(currentSortField) as String)
}
}
orders.sort(sorter)
这不是单行的,而是一种有趣的折衷方案。
更新到更新
这实际上是行不通的,除非所有的变量都是字符串——它们不是。当相关字段是 NSDate 而不是字符串时,这会在 o1.valueForKey(currentSortField) as String 上消失。
【问题讨论】:
-
"然而,在 Swift 中,你不能将字段名称作为字符串动态传递" 对不起,你把我弄丢了——为什么不能呢?
-
@matt 也许我只是措辞错误。在排序闭包中,例如,当您使用 $0.invoiceNumber 时,您不能说 $0."invoiceNumber" 以这种方式传递了字段名称。
-
对,我明白了。而且我想你禁止我说
valueForKey(),即使这个类继承自NSObject? -
确实如此。禁止! ;-) 我在必要时会使用它,但我非常愿意尝试采用 Swift 方法处理所有事情。鉴于它的根源仍然存在并且可行,使用 valueForKey 之类的东西并没有错,但是,我希望有一个我还没有发现的小魔法,那就是纯 Swift。
-
是的,但 Swift 的“魔力”是带走 内省、动态消息形成和类型不可知论。因此,当您举起 Objective-C 并说“在这种情况下 NSWhatever 等价物是单行”时,您似乎是在说:“现在这样做,但没有任何危险但令人上瘾的强大 NSWhatever 功能 一开始就把它做成了单线。”这公平吗?我的意思是,让我们看看你在没有使用任何这些特性的情况下在Objective-C中做到这一点。你的单线是脆脆的,具有 Swift 旨在防止的那种崩溃性。
标签: objective-c arrays sorting swift