Generic Class

NSFetchedResultsController

A controller that you use to manage the results of a Core Data fetch request and display data to the user.

Overview

While table views can be used in several ways, fetched results controllers primarily assist you with a master list view. UITableView expects its data source to provide cells as an array of sections made up of rows. You configure a fetch results controller using a fetch request that specifies the entity, an array containing at least one sort ordering, and optionally a filter predicate. The fetched results controller efficiently analyzes the result of the fetch request and computes all the information about sections in the result set. It also computes all the information for the index based on the result set.

In addition, fetched results controllers:

  • Optionally monitor changes to objects in the associated managed object context, and report changes in the results set to its delegate (see The Controller’s Delegate).

  • Optionally cache the results of its computation so that if the same data is subsequently re-displayed, the work does not have to be repeated (see The Cache).

A controller thus effectively has three modes of operation, determined by whether it has a delegate and whether the cache file name is set.

  • No tracking: The delegate is set to nil. The controller simply provides access to the data as it was when the fetch was executed.

  • Memory-only tracking: the delegate is non-nil and the file cache name is set to nil. The controller monitors objects in its result set and updates section and ordering information in response to relevant changes.

  • Full persistent tracking: the delegate and the file cache name are non-nil. The controller monitors objects in its result set and updates section and ordering information in response to relevant changes. The controller maintains a persistent cache of the results of its computation.

Using NSFetchedResultsController

Creating the Fetched Results Controller

You typically create an instance of NSFetchedResultsController as an instance variable of a table view controller. When you initialize the fetch results controller, you provide four parameters:

  • A fetch request. This must contain at least one sort descriptor to order the results.

  • A managed object context. The controller uses this context to execute the fetch request.

  • Optionally, a key path on result objects that returns the section name. The controller uses the key path to split the results into sections (passing nil indicates that the controller should generate a single section).

  • Optionally, the name of the cache file the controller should use (passing nil prevents caching). Using a cache can avoid the overhead of computing the section and index information.

After creating an instance, you invoke performFetch() to actually execute the fetch.

let context = <#Managed object context#>
let fetchRequest = NSFetchRequest<AAAEmployeeMO>(entityName: "Employee")
// Configure the request's entity, and optionally its predicate
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "<#Sort key#>", ascending: true)]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
    try controller.performFetch()
} catch {
    fatalError("Failed to fetch entities: \(error)")
}

The Controller’s Delegate

If you set a delegate for a fetched results controller, the controller registers to receive change notifications from its managed object context. Any change in the context that affects the result set or section information is processed and the results are updated accordingly. The controller notifies the delegate when result objects change location or when sections are modified (see NSFetchedResultsControllerDelegate). You typically use these methods to update the display of the table view.

The Cache

Where possible, a controller uses a cache to avoid the need to repeat work performed in setting up any sections and ordering the contents. The cache is maintained across launches of your application.

When you initialize an instance of NSFetchedResultsController, you typically specify a cache name. (If you do not specify a cache name, the controller does not cache data.) When you create a controller, it looks for an existing cache with the given name:

  • If the controller can’t find an appropriate cache, it calculates the required sections and the order of objects within sections. It then writes this information to disk.

  • If it finds a cache with the same name, the controller tests the cache to determine whether its contents are still valid. The controller compares the current entity name, entity version hash, sort descriptors, and section key-path with those stored in the cache, as well as the modification date of the cached information file and the persistent store file.

    If the cache is consistent with the current information, the controller reuses the previously-computed information.

    If the cache is not consistent with the current information, then the required information is recomputed, and the cache updated.

Any time the section and ordering information change, the cache is updated.

If you have multiple fetched results controllers with different configurations (different sort descriptors and so on), you must give each a different cache name.

You can purge a cache using deleteCache(withName:).

Implementing the Table View Datasource Methods

You ask the object to provide relevant information in your implementation of the table view data source methods:

override func numberOfSections(in tableView: UITableView) -> Int {
    if let frc = <#Fetched results controller#> {
        return frc.sections!.count
    }
    return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    guard let sections = self.<#Fetched results controller#>?.sections else {
        fatalError("No sections in fetchedResultsController")
    }
    let sectionInfo = sections[section]
    return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = <#Get the cell#>
    guard let object = self.<#Fetched results controller#>?.object(at: indexPath) else {
        fatalError("Attempt to configure cell without a managed object")
    }
    // Configure the cell with data from the managed object.
    return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    guard let sectionInfo = <#Fetched results controller#>?.sections?[section] else {
        return nil
    }
    return sectionInfo.name
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
    return <#Fetched results controller#>?.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
    guard let result = <#Fetched results controller#>?.section(forSectionIndexTitle: title, at: index) else {
        fatalError("Unable to locate section for \(title) at index: \(index)")
    }
    return result
}

Responding to Changes

In general, NSFetchedResultsController is designed to respond to changes at the model layer, by informing its delegate when result objects change location or when sections are modified.

If you allow a user to reorder table rows, then your implementation of the delegate methods must take this into account—see NSFetchedResultsControllerDelegate.

Changes are not reflected until after the controller’s managed object context has received a processPendingChanges() message. Therefore, if you change the value of a managed object’s attribute so that its location in a fetched results controller’s results set would change, its index as reported by the controller would typically not change until the end of the current event cycle (when processPendingChanges() is invoked). For example, the following code fragment would log “same”:

let frc = <#A fetched results controller#>
let managedObject = <#A managed object in frc's fetchedObjects array#>
let beforeIndexPath = frc.indexPath(forObject: managedObject)
managedObject.setValue("Fred", forKey: "name")
let afterIndexPath = frc.indexPath(forObject: managedObject)
if beforeIndexPath?.compare(afterIndexPath!) == .orderedSame {
    print("same")
}

Modifying the Fetch Request

You cannot simply change the fetch request to modify the results. If you want to change the fetch request, you must:

  1. If you are using a cache, delete it (using deleteCache(withName:)).

    Typically you should not use a cache if you are changing the fetch request.

  2. Change the fetch request.

  3. Invoke performFetch().

Handling Object Invalidation

When a managed object context notifies the fetched results controller that individual objects are invalidated, the controller treats these as deleted objects and sends the proper delegate calls.

It’s possible for all the objects in a managed object context to be invalidated simultaneously. (For example, as a result of calling reset(), or if a store is removed from the the persistent store coordinator.) When this happens, NSFetchedResultsController does not invalidate all objects, nor does it send individual notifications for object deletions. Instead, you must call performFetch() to reset the state of the controller then reload the data in the table view (reloadData()).

Subclassing Notes

You create a subclass of this class if you want to customize the creation of sections and index titles. You override sectionIndexTitle(forSectionName:) if you want the section index title to be something other than the capitalized first letter of the section name. You override sectionIndexTitles if you want the index titles to be something other than the array created by calling sectionIndexTitle(forSectionName:) on all the known sections.

Topics

Initializing a Fetched Results Controller

Getting Configuration Information

var fetchRequest: NSFetchRequest<ResultType>

The fetch request used to do the fetching.

var managedObjectContext: NSManagedObjectContext

The managed object context used to fetch objects.

var sectionNameKeyPath: String?

The key path on the fetched objects used to determine the section they belong to.

var cacheName: String?

The name of the file used to cache section information.

var delegate: NSFetchedResultsControllerDelegate?

The object that is notified when the fetched results changed.

class func deleteCache(withName: String?)

Deletes the cached section information with the given name.

Accessing Results

var fetchedObjects: [ResultType]?

The results of the fetch.

func object(at: IndexPath)

Returns the object at the given index path in the fetch results.

func indexPath(forObject: ResultType)

Returns the index path of a given object.

Querying Section Information

var sections: [NSFetchedResultsSectionInfo]?

The sections for the fetch results.

func section(forSectionIndexTitle: String, at: Int)

Returns the section number for a given section title and index in the section index.

Configuring Section Information

func sectionIndexTitle(forSectionName: String)

Returns the corresponding section index entry for a given section name.

var sectionIndexTitles: [String]

The array of section index titles.

Responding to Changes

protocol NSFetchedResultsControllerDelegate

A delegate protocol that describes the methods that will be called by the associated fetched results controller when the fetch results have changed.

protocol NSFetchedResultsSectionInfo

A protocol that defines the interface for section objects vended by a fetched results controller.

struct NSFetchRequestResultType

Constants that specify the possible result types a fetch request can return.

enum NSFetchedResultsChangeType

Constants that specify the possible types of changes that are reported.

Relationships

Generic Constraints

  • ResultType : NSFetchRequestResult
    

Inherits From

Conforms To

See Also

Fetch Requests

class NSFetchRequest

A description of search criteria used to retrieve data from a persistent store.

class NSAsynchronousFetchRequest

A fetch request that retrieves results asynchronously and supports progress notification.

class NSAsynchronousFetchResult

A fetch result object that encompasses the response from an executed asynchronous fetch request.