{"id":16426377,"url":"https://github.com/cgossain/genericresultscontroller","last_synced_at":"2026-02-19T17:31:25.240Z","repository":{"id":56912653,"uuid":"249522172","full_name":"cgossain/GenericResultsController","owner":"cgossain","description":"A generic NSFetchedResultsController replacement for iOS, written in Swift.","archived":false,"fork":false,"pushed_at":"2024-08-13T16:41:18.000Z","size":2827,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-25T04:02:19.066Z","etag":null,"topics":["api","client","connector","controller","coredata","data","database","fetch","firebase","firebase-firestore","firebase-realtime-database","generic","ios","mongodb","nsfetchedresultscontroller","results","source","swift-generics","tableview","ui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cgossain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-23T19:16:00.000Z","updated_at":"2024-08-13T16:40:44.000Z","dependencies_parsed_at":"2023-11-22T18:28:47.086Z","dependency_job_id":"0793605f-ab7b-4f66-a337-c7b6a8b326c5","html_url":"https://github.com/cgossain/GenericResultsController","commit_stats":{"total_commits":113,"total_committers":1,"mean_commits":113.0,"dds":0.0,"last_synced_commit":"ac988d96aacd5283c50c65677a94ebfbcacab406"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/cgossain/GenericResultsController","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgossain%2FGenericResultsController","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgossain%2FGenericResultsController/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgossain%2FGenericResultsController/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgossain%2FGenericResultsController/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cgossain","download_url":"https://codeload.github.com/cgossain/GenericResultsController/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cgossain%2FGenericResultsController/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261416301,"owners_count":23155037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","client","connector","controller","coredata","data","database","fetch","firebase","firebase-firestore","firebase-realtime-database","generic","ios","mongodb","nsfetchedresultscontroller","results","source","swift-generics","tableview","ui"],"created_at":"2024-10-11T08:09:01.167Z","updated_at":"2026-02-19T17:31:25.197Z","avatar_url":"https://github.com/cgossain.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GenericResultsController\n\nThe GenericResultsController is an NSFetchedResultsController replacement for iOS, that is used to manage the results of any data fetch from any data source and to display that data to the user. \n\nThe controller provides an abstracted API that is intentionally simple and makes no assumptions about how you manage your connection to the underlying data store. It also **provides strong support for Swift generics** by enabling you to customize the request and result types. The goal of this project is to provide a data controller with similar functionality to NSFetchedResultsController but with the core functionality (e.g. sectionning and diffing) abstracted out, giving you the ability to interface with any data source using any kind of data model.\n\nThe included example project showcases the core functionality of this library by implementing a custom connection to CoreData. However, the purpose of this library is to provide you with a set of features similar to NSFetchedResultsController while enabling you to plug in a connection to any data source (e.g. CoreData, Firebase, MongoDB, SQL, a REST API, etc.).\n\n![Core Data Fetch \u0026 Diff](./Example/core-data-fetch-and-diff.gif)\n\n[![CI Status](https://img.shields.io/travis/cgossain/GenericResultsController.svg?style=flat)](https://travis-ci.org/cgossain/GenericResultsController)\n[![Version](https://img.shields.io/cocoapods/v/GenericResultsController.svg?style=flat)](https://cocoapods.org/pods/GenericResultsController)\n[![License](https://img.shields.io/cocoapods/l/GenericResultsController.svg?style=flat)](https://cocoapods.org/pods/GenericResultsController)\n[![Platform](https://img.shields.io/cocoapods/p/GenericResultsController.svg?style=flat)](https://cocoapods.org/pods/GenericResultsController)\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\n\n## Requirements\n\n## Installation\n\n### CocoaPods\n\n[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate GenericResultsController into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\npod 'GenericResultsController', '~\u003e 2.4.0'\n```\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. \n\nOnce you have your Swift package set up, adding GenericResultsController as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/cgossain/GenericResultsController.git\", .upToNextMajor(from: \"2.4.0\"))\n]\n```\n\n## Basics\n\n### GenericResultsController\n\n`GenericResultsController` is the controller object that you use to manage the results of a query performed against a data store and to display that data to the user. \n\nYou initialize it with a `DataStore` instance, and it effectively acts as a middle man between your UI and some underlying source of data.\n\nYou must specify the generic parameters for the *result* and *request* types which means they both need to be defined before you can use the results controller. These parameters can be automatically inferred by Swift based on the `DataStore` instance passed during initialization.\n\n### DataStore\n\n`DataStore` is a generic abstract class that enables you to implement a connection to any data source (e.g. local database, cloud database, or even an API).\n\nYou implement a `DataStore` by overriding the `execute(_:)` method which takes a `DataStoreQuery` object as its only argument. You would then typically inspect the `request` parameter of the query object to determine the fetch cirteria, fetch the actual data, and finally deliver the results by enqueuing them into the query object.\n\nThere are also some basic CRUD methods that can be overriden. These are provided for convenience given that your subclass would already have enough context to perform these types of operations.\n\nIn this example, we override the `execute(_:)` method in our data store subclass to fetch data from a CoreData managed object context (see example project for more details).\n\n```\noverride func execute(_ query: DataStoreQuery\u003cEntityType, NSFetchRequest\u003cEntityType\u003e\u003e) {\n    super.execute(query)\n    \n    // perform the query and then call the query's `enqueue` method when\n    // data becomes available\n    //\n    // note if your database supports observing changes to the\n    // executed query you can setup observers here and then call\n    // the query's `enqueue` method to add incremental changes\n    // to the initial fetch results; these would then be picked\n    // up by the results controller providing realtime updates to\n    // the displayed results\n    //\n    // in this example we're executing a core data fetch request, and\n    // then observing the for `NSManagedObjectContextObjectsDidChange` notification\n    // to detect further incrementation changes\n\n    // note, realistically you would use NSFetchedResultsController if you're\n    // using CoreData.\n    \n    // observe incremental changes (since the last save)\n    observersByQueryID[query.id] = NotificationCenter.default.addObserver(\n        forName: .NSManagedObjectContextObjectsDidChange,\n        object: self.managedObjectContext,\n        queue: nil,\n        using: { [unowned self] (note) in\n            // enqueue incremental changes\n            self.handleContextObjectsDidChangeNotification(note, query: query)\n        })\n    \n    // enqueue initial fetch results\n    let fetch = NSAsynchronousFetchRequest(fetchRequest: query.request) { [unowned self] (result) in\n        if self.observersByQueryID[query.id] == nil {\n            return\n        }\n        \n        // enqueue each result into the query\n        guard let objects = result.finalResult else { return }\n        objects.forEach { query.enqueue($0, as: .insert) }\n    }\n    try! managedObjectContext.execute(fetch)\n}\n```\n\n### DataStoreQuery\n\n`DataStoreQuery` is an object that represents a unique instance of a fetch and is initialized with a `DataStoreRequest`.\n\n### DataStoreRequest\n\n`DataStoreRequest` is a type that *request* objects must conform to and its purpose is to define criteria used to retrieve data from a persistent store.\n\nA request object can be anything that conforms to the `DataStoreRequest` protocol which itself has very minimal requirements. The core idea here is that a request object should be something that can be inspected within your data store implementation to gather criteria and build a query to your underlying data source.\n\n### InstanceIdentifiable\n\n`InstanceIdentifiable` is a type that *result* objects must conform to and enables the results controller to uniquely identify result objects.\n\n## Usage\n\n### Initializing a GenericResultsController (snippet from example project)\n\n```\nlet store = CoreDataStore(managedObjectContext: self.managedObjectContext)\n\n// this results controller uses NSFetchRequest as its request type, and a \n// model object called Event as its result type\nvar resultsController: GenericResultsController\u003cEvent, NSFetchRequest\u003cEvent\u003e\u003e!\nresultsController = GenericResultsController(store: store)\n\n// get notified when content is about to change\nresultsController.delegate.controllerWillChangeContent = { (controller) in\n    print(\"Will change content.\")\n}\n\n// get notified when content has changed\nresultsController.delegate.controllerDidChangeContent = { (controller) in\n    print(\"Did change content.\")\n    self.tableView.reloadData()\n    self.refreshControl?.endRefreshing()\n}\n```\n\n### Triggering a Fetch with GenericResultsController (snippet from example project)\n\n```\n// create the fetch request\nlet fetchRequest: NSFetchRequest\u003cEvent\u003e = Event.fetchRequest()\nfetchRequest.returnsObjectsAsFaults = false\n\n// ask the result controller to fetch the results and arrange into sections\nresultsController.performFetch(request: fetchRequest)\n```\n\n### Binding GenericResultsController to your UI (snippet from example project)\n\nIn this example we bind the controller to a UITableView.\n\n```\n// MARK: - UITableViewDataSource\n\nlet cellIdentifier = \"cellIdentifier\"\n\noverride func numberOfSections(in tableView: UITableView) -\u003e Int {\n    return resultsController.sections.count\n}\n\noverride func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n    return resultsController.sections[section].numberOfObjects\n}\n\noverride func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n    let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)\n    let event = try! resultsController.object(at: indexPath)\n    cell.textLabel!.text = event.timestamp!.description\n    return cell\n}\n\noverride func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -\u003e String? {\n    return resultsController.sections[section].name\n}\n\noverride func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {\n    if editingStyle == .delete {\n        let event = try! resultsController.object(at: indexPath)\n        let context = managedObjectContext\n        context.delete(event)\n\n        // save the core data context\n        CoreDataManager.shared.saveContext()\n    }\n}\n```\n\nThere are many more advanced ways to implement this. The example project provides a good starting point. Feel free to reach out if you'd like more details.\n\nI'll be linking to projects that use this library in the future.\n\n## Author\n\nChristian Gossain, cgossain@gmail.com\n\n## License\n\nGenericResultsController is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgossain%2Fgenericresultscontroller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcgossain%2Fgenericresultscontroller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcgossain%2Fgenericresultscontroller/lists"}