Swift Generics,Constraints和KeyPaths

WCB*_*rne 14 generics nssortdescriptor swift keypaths

我知道Swift中泛型的局限性以及它们存在的原因所以这不是关于编译器错误的问题.相反,我偶尔遇到的情况似乎应该可以通过可用资源的某种组合(即泛型,关联类型/协议等)来实现,但似乎无法找到解决方案.

在这个例子中,我试图为NSSortDescriptor提供一个Swift替换(只是为了好玩).当你只有一个描述符时它很完美,但是,正如通常使用NS版本一样,创建一个SortDescriptors数组以对多个键进行排序会很好.

这里的另一个试验是使用Swift KeyPaths.因为那些需要Value类型并且比较需要Comparable值,所以我很难找出在何处/如何定义类型以满足所有要求.

这可能吗?这是我提出的最接近的解决方案之一,但是,正如您在底部看到的那样,在构建阵列时它不足.

同样,我理解为什么这不起作用,但我很好奇是否有办法实现所需的功能.

struct Person {
    let name : String
    let age : Int

}
struct SortDescriptor<T, V:Comparable> {
    let keyPath: KeyPath<T,V>
    let ascending : Bool
    init(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
        self.keyPath = keyPath
        self.ascending = ascending
    }
    func compare(obj:T, other:T) -> Bool {
        let v1 = obj[keyPath: keyPath]
        let v2 = other[keyPath: keyPath]
        return ascending ? v1 < v2 : v2 < v1
    }
}

let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let older = SortDescriptor(\Person.age).compare(obj: jim, other: bob) // true

// Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
var descriptors = [SortDescriptor(\Person.age), SortDescriptor(\Person.name)]
Run Code Online (Sandbox Code Playgroud)

rob*_*off 7

这里的问题是,SortDescriptor是在通用的两个TV,但你只希望它是在通用的T.也就是说,你想要一个SortDescriptor<Person>,因为你关心它比较Person.你不需要a SortDescriptor<Person, String>,因为一旦它被创建,你就不在乎它在某些String属性上进行比较了Person.

可能最简单的"隐藏"方法V是使用闭包来包装关键路径:

struct SortDescriptor<T> {
    var ascending: Bool

    var primitiveCompare: (T, T) -> Bool

    init<V: Comparable>(keyPath: KeyPath<T, V>, ascending: Bool = true) {
        primitiveCompare = { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
        self.ascending = ascending
    }

    func compare(_ a: T, _ b: T) -> Bool {
        return ascending ? primitiveCompare(a, b) : primitiveCompare(b, a)
    }
}

var descriptors = [SortDescriptor(keyPath: \Person.name), SortDescriptor(keyPath: \.age)]
// Inferred type: [SortDescriptor<Person>]
Run Code Online (Sandbox Code Playgroud)

之后,您可能需要一种方便的方法来使用SortDescriptor与对象进行比较的序列.为此,我们需要一个协议:

protocol Comparer {
    associatedtype Compared
    func compare(_ a: Compared, _ b: Compared) -> Bool
}

extension SortDescriptor: Comparer { }
Run Code Online (Sandbox Code Playgroud)

然后我们可以Sequence用一种compare方法扩展:

extension Sequence where Element: Comparer {

    func compare(_ a: Element.Compared, _ b: Element.Compared) -> Bool {
        for comparer in self {
            if comparer.compare(a, b) { return true }
            if comparer.compare(b, a) { return false }
        }
        return false
    }

}

descriptors.compare(jim, bob)
// false
Run Code Online (Sandbox Code Playgroud)

如果您在使用雨燕的使用条件符合,较新的版本,你应该能够有条件地符合SequenceComparer通过改变扩展这个第一行:

extension Sequence: Comparer where Element: Comparer {
Run Code Online (Sandbox Code Playgroud)