It seems like it should be possible to use an array of KeyPath
s as sort keys to sort an array of Swift structs using an arbitrary number of sort keys.
Conceptually, it's simple. You define an array of KeyPaths to a generic object, where the only restriction is that the properties in the keypath be Comparable
.
As long as all the KeyPaths point to properties of the same type, all is well. However, as soon as you try to use KeyPaths that point to elements of different types into the array, it stops working.
See the code below. I create a simple struct with 2 Int properties and a double property. I create an extension to Array that implements a function sortedByKeypaths(_:)
That function specifies a generic type PROPERTY that is Comparable. It takes an array of kepaths to some object Element that specifies properties of type PROPERTY. (Comparable properties.)
As long as you call that function using an array of KeyPaths to properties that are all of the same type, it works perfectly.
However, if you try to pass in array of keypaths to properties of different types, it throws an error "cannot convert value of type '[PartialKeyPath]' to expected argument type '[KeyPath]'"
Because the array contains heterogenious keypaths, the array devolves to type '[PartialKeyPath]` due to type erasure, and you can't use a PartialKeyPath to fetch elements from an array.
Is there a solution to this problem? Not being able to use a heterogenious array of KeyPaths seems like it severely limits the usefulness of Swift KeyPaths
import UIKit
struct Stuff {
let value: Int
let value2: Int
let doubleValue: Double
}
extension Array {
func sortedByKeypaths<PROPERTY: Comparable>(_ keypaths: [KeyPath<Element, PROPERTY>]) -> [Element] {
return self.sorted { lhs, rhs in
var keypaths = keypaths
while !keypaths.isEmpty {
let keypath = keypaths.removeFirst()
if lhs[keyPath: keypath] != rhs[keyPath: keypath] {
return lhs[keyPath: keypath] < rhs[keyPath: keypath]
}
}
return true
}
}
}
var stuff = [Stuff]()
for _ in 1...20 {
stuff.append(Stuff(value: Int(arc4random_uniform(5)),
value2: Int(arc4random_uniform(5)),
doubleValue: Double(arc4random_uniform(10))))
}
let sortedStuff = stuff.sortedByKeypaths([\Stuff.value, \Stuff.value2]) //This works
sortedStuff.forEach { print($0) }
let moreSortedStuff = stuff.sortedByKeypaths([\Stuff.value, \Stuff.doubleValue]) //This throws a compiler error
moreSortedStuff.forEach { print($0) }