GenericResultsController
GenericResultsController 是 iOS 上的 NSFetchedResultsController 替代方案,用于管理从任何数据源获取的任何数据的结果,以及向用户显示这些数据。
该控制器提供一个抽象的 API,这种 API 故意简化且不对您如何管理对底层数据存储的连接进行任何假设。它还通过允许您自定义请求和结果类型,提供了对 Swift 泛型的强大支持。此项目的目标是为具有类似于 NSFetchedResultsController 功能的 数据控制器提供 Abstraction,同时将核心功能(例如大小和差异)抽象出来,因此您可以使用任何类型的数据模型与任何数据源接口。
包含的示例项目通过实现自定义连接到 Core Data 来展示此库的核心功能。然而,此库的目的是为您提供一个类似 NSFetchedResultsController 的功能集,同时让您能够将连接插入到任何数据源(例如 Core Data、Firebase、MongoDB、SQL、REST API 等)。
示例
要运行示例项目,先克隆仓库,然后从 Examples 目录运行 pod install
。
需求
安装
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。有关使用和安装说明,请访问他们的网站。要使用 CocoaPods 将 GenericResultsController 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它。
pod 'GenericResultsController', '~> 2.3.1'
Swift 包管理器
Swift Package Manager 是一种用于自动分发 Swift 代码的工具,已集成到 swift
编译器中。
一旦您设置了 Swift 包,将 GenericResultsController 添加为依赖项就像将其添加到您的 Package.swift
的 dependencies
值一样简单。
dependencies: [
.package(url: "https://github.com/cgossain/GenericResultsController.git", .upToNextMajor(from: "2.3.1"))
]
基本
GenericResultsController
GenericResultsController
是您用来管理对数据存储进行的查询结果并显示给用户的控制器对象。
您需要使用 DataStore
实例来初始化它,它实际上充当了您界面和某些底层数据源之间的中介。
您必须指定 result 和 request 类型的泛型参数,这意味着在您可以使用结果控制器之前,它们都需要被定义。Swift 可以根据初始化过程中传入的 DataStore
实例自动推断这些参数。
DataStore
DataStore
是一个泛型抽象类,它使您能够连接到任何数据源(例如本地数据库、云数据库甚至 API)。
您通过重写 execute(_:)
方法实现 DataStore
,该方法只接受一个 DataStoreQuery
对象作为其唯一参数。通常,您会检查查询对象的 request
参数以确定获取条件,获取实际数据,并将结果通过队列传递给查询对象。
还有一些基本 CRUD 方法可以重写。这些方法提供的目的是出于方便,因为您的子类已经有了足够的环境来执行这些类型的操作。
在这个示例中,我们在数据存储子类中重写了 execute(_:)
方法以从 CoreData 管理对象上下文获取数据(有关更多详细信息,请参阅示例项目)。
override func execute(_ query: DataStoreQuery<EntityType, NSFetchRequest<EntityType>>) {
super.execute(query)
// perform the query and then call the query's `enqueue` method when
// data becomes available
//
// note if your database supports observing changes to the
// executed query you can setup observers here and then call
// the query's `enqueue` method to add incremental changes
// to the initial fetch results; these would then be picked
// up by the results controller providing realtime updates to
// the displayed results
//
// in this example we're executing a core data fetch request, and
// then observing the for `NSManagedObjectContextObjectsDidChange` notification
// to detect further incrementation changes
// note, realistically you would use NSFetchedResultsController if you're
// using CoreData.
// observe incremental changes (since the last save)
observersByQueryID[query.id] = NotificationCenter.default.addObserver(
forName: .NSManagedObjectContextObjectsDidChange,
object: self.managedObjectContext,
queue: nil,
using: { [unowned self] (note) in
// enqueue incremental changes
self.handleContextObjectsDidChangeNotification(note, query: query)
})
// enqueue initial fetch results
let fetch = NSAsynchronousFetchRequest(fetchRequest: query.request) { [unowned self] (result) in
if self.observersByQueryID[query.id] == nil {
return
}
// enqueue each result into the query
guard let objects = result.finalResult else { return }
objects.forEach { query.enqueue($0, as: .insert) }
}
try! managedObjectContext.execute(fetch)
}
DataStoreQuery
DataStoreQuery
是一个表示唯一fetch实例的对象,它使用 DataStoreRequest
进行初始化。
DataStoreRequest
DataStoreRequest
是一个类型,必须使其对象符合,其目的是定义从持久存储检索数据使用的标准。
请求对象可以是任何符合 DataStoreRequest
协议的对象,该协议本身要求非常简单。这里的核心思想是,请求对象应该是可以在您的数据存储实现中进行检查,以收集标准并构建对基础数据源的查询的一件事。
InstanceIdentifiable
InstanceIdentifiable
是一个结果对象必须遵循的类型,并允许结果控制器唯一标识结果对象。
Usage
初始化泛型ResultsController(示例项目代码段)
let store = CoreDataStore(managedObjectContext: self.managedObjectContext)
// this results controller uses NSFetchRequest as its request type, and a
// model object called Event as its result type
var resultsController: GenericResultsController<Event, NSFetchRequest<Event>>!
resultsController = GenericResultsController(store: store)
// get notified when content is about to change
resultsController.delegate.controllerWillChangeContent = { (controller) in
print("Will change content.")
}
// get notified when content has changed
resultsController.delegate.controllerDidChangeContent = { (controller) in
print("Did change content.")
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
使用泛型ResultsController触发获取操作(示例项目代码段)
// create the fetch request
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
// ask the result controller to fetch the results and arrange into sections
resultsController.performFetch(request: fetchRequest)
将泛型ResultsController绑定到你的UI(示例项目代码段)
在这个例子中,我们将控制器绑定到UITableView。
// MARK: - UITableViewDataSource
let cellIdentifier = "cellIdentifier"
override func numberOfSections(in tableView: UITableView) -> Int {
return resultsController.sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultsController.sections[section].numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
let event = try! resultsController.object(at: indexPath)
cell.textLabel!.text = event.timestamp!.description
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return resultsController.sections[section].name
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let event = try! resultsController.object(at: indexPath)
let context = managedObjectContext
context.delete(event)
// save the core data context
CoreDataManager.shared.saveContext()
}
}
实现该方法有许多更高级的方式。示例项目提供了一个很好的起点。如果您想了解更多细节,请随时联系。
我将在未来链接使用此库的项目。
作者
Christian Gossain, [email protected]
许可协议
GenericResultsController 计划遵守 MIT 许可协议。更多详情请参阅 LICENSE 文件。