Swift Generics,Constraints和KeyPaths

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift Generics,Constraints和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)]
答案

这里的问题是SortDescriptorTV上都是通用的,但你只希望它在T上是通用的。也就是说,你想要一个SortDescriptor<Person>,因为你关心它比较Person。你不需要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>]

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

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

extension SortDescriptor: Comparer { }

然后我们可以用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

如果您使用具有条件一致性的较新版本的Swift,您应该能够通过将扩展的第一行更改为有条件地将Sequence符合Comparer

extension Sequence: Comparer where Element: Comparer {
另一答案

扩展@Rob Mayoff的答案,这是一个完整的排序解决方案

enum SortDescriptorComparison {
    case equal
    case greaterThan
    case lessThan
}

struct SortDescriptor<T> {
    private let compare: (T, T) -> SortDescriptorComparison
    let ascending : Bool

    init<V: Comparable>(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
        self.compare = {
            let v1 = $0[keyPath: keyPath]
            let v2 = $1[keyPath: keyPath]
            if v1 == v2 {
                return .equal
            } else if v1 > v2 {
                return .greaterThan
            } else {
                return .lessThan
            }
        }
        self.ascending = ascending
    }

    func compare(v1:T, v2:T) -> SortDescriptorComparison {
        return compare(v1, v2)
    }
}

extension Array {

    mutating func sort(sortDescriptor: SortDescriptor<Element>) {
        self.sort(sortDescriptors: [sortDescriptor])
    }

    mutating func sort(sortDescriptors: [SortDescriptor<Element>]) {
        self.sort() {
            for sortDescriptor in sortDescriptors {
                switch sortDescriptor.compare(v1: $0, v2: $1) {
                case .equal:
                    break
                case .greaterThan:
                    return !sortDescriptor.ascending
                case .lessThan:
                    return sortDescriptor.ascending
                }
            }
            return false
        }
    }
}

extension Sequence {

    func sorted(sortDescriptor: SortDescriptor<Element>) -> [Element] {
        return self.sorted(sortDescriptors: [sortDescriptor])
    }

    func sorted(sortDescriptors: [SortDescriptor<Element>]) -> [Element] {
        return self.sorted() {
            for sortDescriptor in sortDescriptors {
                switch sortDescriptor.compare(v1: $0, v2: $1) {
                case .equal:
                    break
                case .greaterThan:
                    return !sortDescriptor.ascending
                case .lessThan:
                    return sortDescriptor.ascending
                }
            }
            return false
        }
    }
}

struct Person {
    let name : String
    let age : Int
}

let jim = Person(name: "Jim", age: 25)
let bob = Person(name: "Bob", age: 30)
let tim = Person(name: "Tim", age: 25)
let abe = Person(name: "Abe", age: 20)

let people = [tim, jim, bob, abe]
let sorted = people.sorted(sortDescriptors: [SortDescriptor(Person.age), SortDescriptor(Person.name)])

print(sorted) //Abe, Jim, Time, Bob
另一答案

这是一个几乎纯粹功能性的解决方案:

// let's add some semantics
typealias SortDescriptor<T> = (T, T) -> Bool

// type constructor for SortDescriptor
func sortDescriptor<T, U: Comparable>(keyPath: KeyPath<T, U>, ascending: Bool) -> SortDescriptor<T> {
    return { ascending == ($0[keyPath: keyPath] < $1[keyPath: keyPath]) }
}

// returns a function that can sort any two element of type T, based on
// the provided list of descriptors
func compare<T>(with descriptors: [SortDescriptor<T>]) -> (T, T) -> Bool {
    func innerCompare(descriptors: ArraySlice<SortDescriptor<T>>, a: T, b: T) -> Bool {
        guard let descriptor = descriptors.first else { return false }
        if descriptor(a, b) { return true }
        else if descriptor(b, a) { return false }
        else { return innerCompare(descriptors: descriptors.dropFirst(1), a: a, b: b) }
    }
    return { a, b in innerCompare(descriptors: descriptors[0...], a: a, b: b) }
}

// back to imperative, extend Sequence to allow sorting with descriptors
extension Sequence {
    func sorted(by descriptors: [SortDescriptor<Element>]) -> [Element] {
        return sorted(by: compare(with: descriptors))
    }
}

它基于小型,可重用的功能,如compare(),可以在其他范围内轻松重用。

用法示例:

struct Person {
    let name : String
    let age : Int
}

let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let alice = Person(name: "Alice", age: 35)
let aly = Person(name: "Aly", age: 32)

let descriptors = [sortDescriptor(keyPath: Person.age, ascending: false),
                   sortDescriptor(keyPath: Person.name, ascending: true)]
let persons = [jim, bob, alice, aly]
print(persons.sorted(by: descriptors))

以上是关于Swift Generics,Constraints和KeyPaths的主要内容,如果未能解决你的问题,请参考以下文章

NSLayoutConstraint 的 Unsatisfiable Constraint 以编程方式更改 - swift

heightAnchor.constraint 不起作用(使用 Swift 的 Apple FoodTracker 教程)

如何通过循环 swift 5 为滚动视图内的 imageView 设置 trailingAnchor.constraint

Swift 打印锚常量值

在 Swift 中枚举视图的 NSLayoutConstraints?

swift 类型备份