Swift:使用关键路径创建自定义查询函数教程

199 阅读3分钟

作为一种相当严格的静态编译语言,最初看起来Swift并没有提供太多的语法定制,但实际上情况远非如此。通过自定义和重载运算符关键路径函数/结果生成器等功能,我们有很多机会为特定的使用情况调整Swift的语法。

当然,也可以肯定地说,任何形式的语法定制都应该非常谨慎地对待,因为如果我们不小心,非标准的语法也很容易成为混乱的来源。但是,在某些情况下,这种权衡可能是值得的,而且可以让我们制作 "微型DSL",实际上可以帮助我们使代码更加清晰,而不是相反。

被否定的布尔密钥路径

来看看这样一个案例,假设我们正在开发一个用于管理、过滤和排序文章的应用,其特点是:Article 数据模型:

struct Article {
    var title: String
    var body: String
    var category: Category
    var isRead: Bool
    ...
}

现在我们假设在我们的代码库中一个非常常见的任务是过滤各种集合,每个集合都包含上述模型的实例。一种方法是利用这样一个事实,即任何Swift的关键路径字都可以自动转换为一个函数,这让我们在对任何布尔属性进行过滤时使用以下紧凑的语法,例如本例中的isRead

let articles: [Article] = ...
let readArticles = articles.filter(\.isRead)

这真的很好,然而,上述语法只能在我们想与true 进行比较时使用--这意味着如果我们想创建一个类似的包含所有未读文章的过滤数组,那么我们就必须使用一个闭包(或传递一个函数)来代替:

let unreadArticles = articles.filter { !$0.isRead }

这当然不是什么大问题,但如果上述操作是我们在代码库的许多不同地方执行的,那么我们就可能开始问自己。"如果我们也能对被否定的布尔使用同样漂亮的关键路径语法,那不是很好吗?"

这就是语法定制的概念所在。通过实现下面的前缀函数,我们实际上可以创建一个小的调整,让我们使用关键路径,无论我们是与true 还是与false 进行比较:

prefix func !<T>(keyPath: KeyPath<T, Bool>) -> (T) -> Bool {
    return { !$0[keyPath: keyPath] }
}

以上基本上是内置的! 前缀运算符的重载,这使得我们可以将该运算符应用于任何Bool 关键路径,以便将其变成一个否定(或翻转)其值的函数--这反过来让我们像这样计算我们的unreadArticles 数组:

let unreadArticles = articles.filter(!\.isRead)

这很酷,而且并没有使我们的代码变得混乱,因为我们使用! 操作符的方式与它的默认使用方式一致--否定一个布尔表达式。

基于关键路径的比较

现在,为了更进一步,让我们也可以使用关键路径来形成过滤器查询,将一个给定的属性与任何类型的Equatable 值进行比较。例如,如果我们想根据每篇文章的category 来过滤我们的articles 数组,这将变得非常有用。该属性的类型,Category ,目前被定义为一个枚举,看起来像这样:

extension Article {
    enum Category {
        case fullLength
        case quickReads
        case basics
        ...
    }
}

就像我们之前用一个关键路径的变体重载了! 操作符一样,我们也可以对== 操作符做同样的事情,就像以前一样,我们将返回一个Bool-返回的闭包,然后可以直接传递给API,如filter

func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> (T) -> Bool {
    return { $0[keyPath: lhs] == rhs }
}

有了上面的方法,我们现在可以轻松地使用基于密钥路径的比较来过滤任何集合,就像这样:

let fullLengthArticles = articles.filter(\.category == .fullLength)

结论

取决于你问谁,Swift让我们通过几个轻量级的重载轻松创建上述功能,这要么是非常棒的,要么是令人难以置信的。我倾向于介于两者之间,并认为我们可以对Swift的语法做一些特定领域的小调整,这确实很好,但同时,我认为这些调整应该始终以使我们的代码更简单而不是更复杂为目标。

就像我在Swift by Sundell上写的所有东西一样,我个人在一些需要做大量集合过滤的项目中使用了上述技术,它非常出色,但除非我对这种功能有强烈的需求,否则我不会部署它。