后台搜索和排序

这个 Swift 包允许您基于您提供的 KeyPath,在后台线程中对任何 Sendable 数组进行 搜索排序,并且以 类型安全 的方式进行。

安装

您应该在您的项目中使用 Swift Package Manager 导入此包。

Xcode 项目

要将此包导入到您现有的项目,只需打开 File -> Add Package Dependencies 并复制粘贴 URL 到 SPM。

Swift 包

要导入到 Swift 包,请将以下 URL 添加到您的包依赖项中

.package(url: "https://github.com/mohammad-nej/SearchAndSort", .upToNextMajor(from: "1.0.1")),

您还应该将其作为产品添加到您的 targets 中: .product(name: "SearchAndSort", package: "SearchAndSort"),

此包基于 KeyPathsSendable 类型提供了不同类型的搜索和排序功能。

排序

假设我们有一个 Student 类型

struct Student : Sendable { 
  let name : String
  let age : Int
  let grade : Double
  let birthDate : Date
}

如果我们想要基于 nameStudent 数组进行排序

 let students : [Studnet] = []
 // we fill the students array
 let nameKey = SortableKeyPath(\Student.name)
 let sortedArray = nameKey.sort(students, order: .ascending)

为了创建 SortableKeyPath,您的类型应该是 Sendable,并且 key 应该遵循 Comparable 协议,否则您将收到编译时错误。

类型擦除

如果您希望能够基于多个 Key 进行排序,您可以使用 AnySortableKey 类型擦除,这将擦除您的 Key 类型

let nameKey = SortableKeyPath(\Student.name)
let ageKey = SortableKeyPath(\Student.age)
let keys : [AnySortableKey] = [.init(nameKey), .init(ageKey)]

注意: 这只会擦除 Key 的类型,因此您不能在同一个数组中存储不同模型的 SortableKeyPath

搜索

为了能够基于 KeyPath 进行搜索,您需要提供一个遵循 Stringifier 协议的类型。此协议只有一个方法

public protocol Stringifier : Sendable  {
   
   associatedtype Model 
   
   func stringify(_ model : Model) -> [String]
}

示例

假设我们要为 Int 类型实现此协议

 struct IntStringifier : Stringifier, Sendable {
     func stringify(_ model: Int) -> [String] {
        var results : [String] = []
        results.append(model.formatted())
        results.append(model.description)
        return results
    }
}

这样,当 BackgroundSearcher 迭代您的模型时,它会将每个 Int 转换为一个 字符串数组,并将其与您的搜索查询匹配。例如,使用此 IntStringifier,如果用户输入 "2000" 或 "2,000" 作为查询,他们都将获得匹配。

好消息: 任何遵循 CustomStringConvertibale 协议的类型都将自动接收一个 Stringifier,该 Stringifier 将返回其 description

创建 SearchableKey

话虽如此,让我们创建 SearchableKey

 let nameKey = SearchableKeyPath(\Student.name)
 let ageKey = SearchableKeyPath(\Student.age)
 let dateKey = SearchableKeyPath( \Student.birthDate, stringifier: MyOwnStringifier())

我们现在可以使用这些 key 在任何 Student 类型的数组上进行搜索

 let searchResult = await nameKey.search(in:studnets , for: "John")
 let searchResult = await ageKey.search(in:studnets , for: "John", strategy: .exact)

注意: 如果在完成之前取消搜索函数,它将返回 nil;如果找不到任何内容,它将返回一个空数组。 注意: 您可以在不同的匹配策略之间进行选择,默认值是 .contains

类型擦除

就像排序一样,您也可以使用 AnySearchableKey 来类型擦除您的 key 并将它们存储在一个数组中

let keys : [AnySearchableKey] = [.init(nameKey), .init(ageKey)]

BackgroundSearcher

这是一个 actor,它实际为您执行搜索。它将根据您的数组大小在内部创建 Task.detached

您可以将您的模型和 key 传递给此类型,并让它为您执行搜索。

let searcher = BackgroundSearcher(models: studnets,keys: [.init(nameKey),.init(ageKey)])
await searcher.search("John" ,withKey: [.init(nameKey)] , strategy: .prefix)

注意

  1. 如果您没有为 withKey: 参数提供值,它将迭代您在初始化期间提供的所有 Key
  2. 当您直接从 SearchableKeyPath 调用 search 时,会在内部创建一个 BackgroundSearcher 实例。
  3. 如果您的数组大小超过 1500BackgroundSearcher 将创建多个 Task,您可以通过设置 minimumElementsToMultiThread 来更改此数字。

Sorter

就像 BackgroundSearcher 一样,Sorter 将允许您在一个地方存储所有模型和 key

let nameSort = SortableKeyPath(\Student.name)
let ageSort = SortableKeyPath(\Student.age)
let sorter = Sorter(models: students, keys: [.init(nameSort),.init(ageSort))

注意:BackgroundSearcher 不同,无论您的数组大小如何,Sorter 始终会创建一个 Task

TitledKey

此类型可用于为您的 key 提供标题(例如,您想在 UI 中显示它)。

let titledNameKey = TitledKey(title: "Name", key: \Student.name)

此类型可以被 SearchableKeyPath 替换,如果您的提供的 Key 遵循 Comparable 协议,您也可以使用它来代替 SortableKeyPath

因此它可以作为 BackgroundSearcherAnySearchableKeySorterAnySortableKey(如果 key 是 Comparable)的输入。使用此类型的唯一缺点是,如果您只想进行排序,则始终必须为您的 Key 类型提供 Stringifier

AnyKey

此类型也是一种类型擦除,可以包含 SortableKeySearchableKeyTitledKey,甚至可以直接从 KeyPath 创建。

        let sortableKey = SortableKeyPath(\Student.name, order: .ascending)
        let birthDayKey = SearchableKeyPath(\Student.birthDate,stringifier: .persian)
        let nameKey = TitledKey(title: "Name", key: \Student.name)
        
        let anykey = AnyKey(birthDayKey,sortOrder: .ascending)
        let anyKey2 = AnyKey(nameKey,sortOrder:.ascending)
        let anyKey3 = AnyKey(sortableKey) 

类型安全

此包是完全 类型安全 的,您不会出错,并且会得到编译时错误。它也基于 Swift 6,因此它是完全并发安全的。

版权

此包免费提供使用。如果您想了解更多信息,请随时在 GitHub 上或使用我的电子邮件联系我:mohammad.nej@gmail.com