使用 Key Paths 創(chuàng)建自定義查詢函數(shù)
本文轉(zhuǎn)載自微信公眾號(hào)「Swift社區(qū)」,作者Swift社區(qū)。轉(zhuǎn)載本文請(qǐng)聯(lián)系Swift社區(qū)公眾號(hào)。
前言
作為一個(gè)相當(dāng)嚴(yán)格,靜態(tài)編譯的語言,Swift 可能不會(huì)在語法自定義方面提供許多渠道,但這實(shí)際上確正好相反。通過如何在 Swift 中自定義操作符,Swift 中 key paths 的能力,函數(shù)/結(jié)果構(gòu)建器 等功能,我們有很多機(jī)會(huì)為特定用例進(jìn)行調(diào)整 Swift 的語法。
當(dāng)然,無可爭議的是,任何類型的語法定制都應(yīng)小心謹(jǐn)慎地,因?yàn)槿绻覀儾恍⌒?,非?biāo)準(zhǔn)語法也可能很容易成為混亂的源泉。但是,在某些情況下,權(quán)衡可能是值得的,并且可以易于讓我們制作類似 DSL 這種可以幫助我們使代碼更清晰的語法。
否定布爾值的 key pahts
讓我們查看一個(gè)這樣的案例,說我們正在研究一個(gè)應(yīng)用程序,用于管理,過濾和排序文章,其中包含以下 Article 數(shù)據(jù)模型:
- struct Article {
- var title: String
- var body: String
- var category: Category
- var isRead: Bool
- ...
- }
現(xiàn)在讓我們看一下我們的代碼庫中的一個(gè)非常常見的任務(wù)是過濾各種集合,每個(gè)集合包含上述模型的實(shí)例。這樣做的一種方法是利用任何 "Swift key paths 表達(dá)式可以自動(dòng)轉(zhuǎn)換為函數(shù)" 的功能,這讓我們在過濾任何布爾屬性時(shí), 可以使用如下在篩選 isread 時(shí)的凝練的語法:
- let articles: [Article] = ...
- let readArticles = articles.filter(\.isRead)
這真的是非常好,但是,只有在我們想要與 true 比較時(shí)才能使用以上語法 ——如果我們想創(chuàng)建包含所有未讀文章的類似過濾的數(shù)組,那么我們必須使用閉包(或 傳入一個(gè)函數(shù)[1])代替:
- let unreadArticles = articles.filter { !$0.isRead }
這肯定不是一個(gè)大問題,但如果上述操作是我們在代碼上的許多不同地方上演的東西,那么我們可能會(huì)開始問自己:“如果我們也可以使用否定的布爾值的 key paths 語法會(huì)不會(huì)更好?“
這就是語法自定義的概念進(jìn)來的地方。通過實(shí)現(xiàn)以下前綴函數(shù),我們實(shí)際上可以創(chuàng)建一個(gè)小小的調(diào)整,這將讓我們不用擔(dān)心 true 或 false 的使用 key paths:
- prefix func !<T>(keyPath: KeyPath<T, Bool>) -> (T) -> Bool {
- return { !$0[keyPath: keyPath] }
- }
以上基本上就是是重載內(nèi)置的 ! 前置操作符,讓其可以應(yīng)用于任何 Bool key paths,以便將其轉(zhuǎn)換為否定(或翻轉(zhuǎn))其值的函數(shù) ——現(xiàn)在我們可以計(jì)算我們的 UnreadArticles 數(shù)組了:
- let unreadArticles = articles.filter(!\.isRead)
基于 key paths 的比較
現(xiàn)在,進(jìn)一步采取措施,讓我們也可以使用 key paths 來形成篩選器查詢,該篩選器查詢將給定屬性與任何 Equatable 的值進(jìn)行比較。例如,如果我們想要根據(jù)每篇文章的類別過濾我們的文章類別,那將變得有用。該屬性,類別的類型目前被定義為如下所示的枚舉:
- extension Article {
- enum Category {
- case fullLength
- case quickReads
- case basics
- ...
- }
- }
就像我們之前重載的 ! 操作符一樣,我們也可以用 == 運(yùn)算符進(jìn)行同樣的事情,我們將返回一個(gè)返回 Bool 的閉包,然后可以直接傳遞給篩選器(如 filter 過濾器):
- func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> (T) -> Bool {
- return { $0[keyPath: lhs] == rhs }
- }
通過以上重載,我們現(xiàn)在可以使用基于 key paths 的比較輕松過濾任何集合,如下所示:
- let fullLengthArticles = articles.filter(\.category == .fullLength)
結(jié)語
Swift 讓我們通過幾個(gè)輕量級(jí)重載輕松創(chuàng)建上述功能的事實(shí)是非常棒的或令人難以置信的。我傾向于在中間的某個(gè)地方停下,認(rèn)為我們確實(shí)可以讓部分 Swift 的語法調(diào)整為適合我們的編寫,但同時(shí),我認(rèn)為應(yīng)該始終盯緊我們使 diam 更簡單的目標(biāo)來調(diào)整這些代碼。
參考資料
[1]傳入一個(gè)函數(shù): https://www.swiftbysundell.com/articles/first-class-functions-in-swift/