How NSFetchRequest, NSFetchedResultsController work in Core Data Framework

NSFetchedResultsControllerDelegate provides mechanisms for tracking changes that happen to the objects of our NSFetchRequest in the model. Let’s refer to the UITableView example to learn how to display changes that have taken place in the model while maintaining proper UI representation.For display purposes, I highlighted the fetched objects with a dotted line.As you may already know, Core Data is a framework for data storage and interaction. There is a ton of info about Core Data on the net if you want to learn more about the framework in general.

It is common for beginners (especially developers from Stack Overflow) to shy away from using the Core Data Framework because it comes across as complicated, or to employ only a minor portion of its capabilities. In practice, knowing the basic functions of this framework’s classes allows the developer to easily work with the model.

In this article, I want to focus on the following aspects:

  • We will examine the NSFetchRequest class, which creates requests to fetch the existing data from the model. We will go over its basic properties and present in-use cases.
  • We will take a detailed look at NSFetchedResultsController, its functions and how it conveniently displays fetched data with the help of NSFetchRequest, using UITableView as an example.


Demo project description

Our guinea pig will be a rather simplistic demo project that includes Model and ViewController that contains UITableView. For demonstrative purposes, we will use an ordinary grocery list with names and prices.


Model

The model will contain two entities: Products with the attributes name and price, and FavoriteProducts that inherits these attributes.


0a3b26d2fcb0482aaf170e5d0d12f39a.png


As an example, we’ll fill up our database with a certain number of groceries, each with a random price (up to 1000) and a product name from the following list: “Milk”, “Soda”, “Donut”, “Banana”, “Cheddar Cheese”, “Parmesan Cheese”, “Flour”, “Oatmeal”.


Controller

We’ll use the following code in the controller to initialize and display the table in full screen.

Objective-C

- (UITableView *)tableView {
    if (_tableView != nil) return _tableView;
    _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
    _tableView.backgroundColor = [UIColor whiteColor];
    _tableView.dataSource = self;
    return _tableView;
}
- (void)loadView {
    [super loadView];
    [self.view addSubview:self.tableView];
}
- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    _tableView.frame = self.view.frame;
}

Swift

var tableView: UITableView = {
     let tableView = UITableView(frame: CGRectZero, style: .Grouped)
     tableView.backgroundColor = UIColor.whiteColor()
     return tableView
}()
override func loadView() {
    super.loadView()
    self.view.addSubview(tableView)
}
override func viewDidLoad() {
    super.viewDidLoad()
    tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    tableView.frame = self.view.frame
}

Data fetching

The data is fetched from the model with the help of the  executeFetchRequest(_:)  method argument. The centerpiece of our article is the NSFetchRequest method argument.

Objective-C

NSManagedObjectContext *context = [[CoreDataManager instance] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Products"
                                                     inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entityDescription;
NSError *error = nil;
NSArray* objects = [context executeFetchRequest:request error:&error];

Swift

let context = CoreDataManager.instance.managedObjectContext
let entityDescription = NSEntityDescription.entityForName("Products", inManagedObjectContext: context)
let request = NSFetchRequest()
request.entity = entityDescription
do {
    let objects = try context.executeFetchRequest(request)
} catch {
    fatalError("Failed to fetch employees: \(error)")
}

By default, the type returned by the "Donut" method is the object array of the NSManagedObject class. For display purposes, let’s print out the entries fetched from our model in a temporary output format.

The result

NAME: Donut, PRICE: 156
NAME: Soda, PRICE: 425
NAME: Soda, PRICE: 85
NAME: Cheddar Cheese, PRICE: 400
NAME: Flour, PRICE: 920
NAME: Parmesan Cheese, PRICE: 861
NAME: Soda, PRICE: 76
NAME: Milk, PRICE: 633
NAME: Soda, PRICE: 635
NAME: Parmesan Cheese, PRICE: 718
NAME: Donut, PRICE: 701
NAME: Soda, PRICE: 176
NAME: Banana, PRICE: 731
NAME: Parmesan Cheese, PRICE: 746
NAME: Flour, PRICE: 456
NAME: Flour, PRICE: 519
NAME: Cheddar Cheese, PRICE: 221
NAME: Flour, PRICE: 560
NAME: Parmesan Cheese, PRICE: 646
NAME: Donut, PRICE: 492
NAME: Banana, PRICE: 185
NAME: Soda, PRICE: 539
NAME: Parmesan Cheese, PRICE: 872
NAME: Banana, PRICE: 972
NAME: Donut, PRICE: 821
NAME: Milk, PRICE: 409
NAME: Banana, PRICE: 334
NAME: Milk, PRICE: 734
NAME: Soda, PRICE: 448
NAME: Parmesan Cheese, PRICE: 345

Basic methods and properties of the NSFetchRequest class

As its name suggests, the NSFetchRequest class is used to request data fetches from the model. This tool allows you to set object filtering and sorting rules at the fetching stage, so the operation becomes several times quicker and more efficient than fetching every object first (what if there are 10000 objects or more?) and then manually sorting or filtering the data we require.

When we know the basic properties of this class, we can easily manage requests and get a specific fetch without developing additional algorithms and crutches as everything is already implemented in Core Data. Let’s get started.


sortDescriptors

Objective-C

@property (nonatomic, strong) NSArray <NSSortDescriptor *> *sortDescriptors

Swift

var sortDescriptors: [NSSortDescriptor]?

It seems appropriate to begin reviewing the properties of the class from sortDescriptors, which is an array of objects of the NSSortDescriptor class that are used by the sorting mechanism. You can find the guide on using the sort descriptors on the Apple portal. This property accepts the sort descriptors array, allowing us to use several sorting rules. In this case, priorities are equivalent to queue rules (FIFO, First In-First Out): the lower the object’s index in the array, the higher the sorting priority.


In-use example

Printing out all the objects the way we did before leads to a chaotic and hard-to-read output. For convenience, let’s sort this list: first, by grocery product name, specifying the name attribute as the sort key, and then by price. We want to sort by name in alphabetical order and by price in ascending order; for this, we have to set the ascending value of both predicates to Boolean true (Boolean false is used for descending order).

Objective-C

NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSSortDescriptor *priceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"price" ascending:YES];
fetchRequest.sortDescriptors = @[nameSortDescriptor, priceSortDescriptor];

Swift

let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let priceSortDescriptor = NSSortDescriptor(key: "price", ascending: true)
fetchRequest.sortDescriptors = [nameSortDescriptor, priceSortDescriptor]

The result of the sorting operation

NAME: Banana, PRICE: 185
NAME: Banana, PRICE: 334
NAME: Banana, PRICE: 731
NAME: Banana, PRICE: 972
NAME: Donut, PRICE: 156
NAME: Donut, PRICE: 492
NAME: Donut, PRICE: 701
NAME: Donut, PRICE: 821
NAME: Soda, PRICE: 76
NAME: Soda, PRICE: 85
NAME: Soda, PRICE: 176
NAME: Soda, PRICE: 425
NAME: Soda, PRICE: 448
NAME: Soda, PRICE: 539
NAME: Soda, PRICE: 635
NAME: Parmesan Cheese, PRICE: 345
NAME: Parmesan Cheese, PRICE: 646
NAME: Parmesan Cheese, PRICE: 718
NAME: Parmesan Cheese, PRICE: 746
NAME: Parmesan Cheese, PRICE: 861
NAME: Parmesan Cheese, PRICE: 872
NAME: Cheddar Cheese, PRICE: 221
NAME: Cheddar Cheese, PRICE: 400
NAME: Milk, PRICE: 409
NAME: Milk, PRICE: 633
NAME: Milk, PRICE: 734
NAME: Flour, PRICE: 456
NAME: Flour, PRICE: 519
NAME: Flour, PRICE: 560
NAME: Flour, PRICE: 920

predicate

Objective-C

@property (nonatomic, strong) NSPredicate *predicate

Swift

var predicate: NSPredicate?


The next property we’ll examine is predicate of the NSPredicate class, a quick and powerful data filtering tool. There’s an excellent guide to using predicate from Apple. Data filtering on request is done thanks to the predicate’s distinctive string syntax, described in the aforementioned guide.


In-use example

Let’s start with a simple example. We’d like to know the prices for pepperoni sausages in our grocery list. For this, we need to specify in the predicate that we want all the objects that have the "Cheddar Cheese” string in their name attribute value.


Objective-C

 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", @"Cheddar Cheese"];
 fetchRequest.predicate = predicate;

Swift

let predicate = NSPredicate(format: "name == %@", "Cheddar Cheese")
fetchRequest.predicate = predicate

The result

NAME: Cheddar Cheese, PRICE: 400
NAME: Cheddar Cheese, PRICE: 221


Take note that to create a correct predicate with a == equal operator you have to specify the string value precisely as it is case sensitive.

But what if we want to check the prices for all kinds of sausages, including but not limited to (молочной) pepperoni? In this case, we have to use the CONTAINS operator (left expression contains right expression) and add the [cd] keywords that turn off case and diacritics sensitivity. We can also use several conditions with the help of AND operator. Let’s also add a 500 price limit to our results as well.


Objective-C

 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@ AND price < %d", @"колбаса", 500];
 fetchRequest.predicate = predicate;

Swift

let predicate = NSPredicate(format: "name CONTAINS[cd] %@ AND price < %d", "колбаса", 500)
fetchRequest.predicate = predicate

The result

NAME: Cheddar Cheese, PRICE: 400
NAME: Cheddar Cheese, PRICE: 221
NAME: Parmesan Cheese, PRICE: 345

fetchLimit

Objective-C

@property (nonatomic) NSUInteger fetchLimit

Swift

var fetchLimit: Int

The fetchLimit property allows you to limit the amount of fetched objects.


In-use example

To demonstrate this property, let’s retrieve the 12 cheapest products from our groceries list. For this, we’ll add price sorting and set a limit of 12 to the amount of fetched objects.

Objective-C

// sorted by price
NSSortDescriptor *priceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"price" ascending:YES];
fetchRequest.sortDescriptors = @[priceSortDescriptor];
// amount limit = 12
fetchRequest.fetchLimit = 12;

Swift

// sorted by price
let priceSortDescriptor = NSSortDescriptor(key: "price", ascending: true)
fetchRequest.sortDescriptors = [priceSortDescriptor]
// amount limit = 12
fetchRequest.fetchLimit = 12

The result

NAME: Soda, PRICE: 76
NAME: Soda, PRICE: 85
NAME: Donut, PRICE: 156
NAME: Soda, PRICE: 176
NAME: Banana, PRICE: 185
NAME: Cheddar Cheese, PRICE: 221
NAME: Banana, PRICE: 334
NAME: Parmesan Cheese, PRICE: 345
NAME: Cheddar Cheese, PRICE: 400
NAME: Milk, PRICE: 409
NAME: Soda, PRICE: 425
NAME: Soda, PRICE: 448

fetchOffset

Objective-C

@property (nonatomic) NSUInteger fetchOffset

Swift

var fetchOffset: Int

This property can be used to shift the fetch results by a set amount of objects.


In-use example

To demonstrate how this property works, we’ll take the previous request and shift it by two objects. As a result, we’ll get 12 objects with the first 2 of them skipped and the next 2 added at the end, as if we’ve shifted the results table by two cells.


Objective-C

fetchRequest.fetchOffset = 2;

Swift

fetchRequest.fetchOffset = 2


For display purposes, I highlighted the fetched objects with a dotted line.

Before:                                     After:
                                            NAME: Soda, PRICE: 76
                                            NAME: Soda, PRICE: 85
---------------------------------------------------------------------------------
NAME: Soda, PRICE: 76                       NAME: Donut, PRICE: 156
NAME: Soda, PRICE: 85                       NAME: Soda, PRICE: 176
NAME: Donut, PRICE: 156                     NAME: Banana, PRICE: 185
NAME: Soda, PRICE: 176                      NAME: Cheddar Cheese, PRICE: 221
NAME: Banana, PRICE: 185                    NAME: Banana, PRICE: 334
NAME: Cheddar Cheese, PRICE: 221            NAME: Parmesan Cheese, PRICE: 345
NAME: Banana, PRICE: 334                    NAME: Cheddar Cheese, PRICE: 400
NAME: Parmesan Cheese, PRICE: 345           NAME: Milk, PRICE: 409
NAME: Cheddar Cheese, PRICE: 400            NAME: Soda, PRICE: 425
NAME: Milk, PRICE: 409                      NAME: Soda, PRICE: 448
NAME: Soda, PRICE: 425                      NAME: Flour, PRICE: 456
NAME: Soda, PRICE: 448                      NAME: Donut, PRICE: 492
---------------------------------------------------------------------------------
NAME: Flour, PRICE: 456                       ...
NAME: Donut, PRICE: 492
...

fetchBatchSize

Objective-C

@property (nonatomic) NSUInteger fetchBatchSize

Swift

var fetchBatchSize: Int


The fetchBatchSize property is used to adjust the amount of objects that can be fetched at once from the database (Persistent Store) that the Core Data Framework is working with (SQLite, XML, etc). The best value to set is different for each specific case, so this property might either speed up or slow down your database performance.

Let’s say we are working with UITableView and there are more than 10000 objects in our model. It’ll take some time to fetch all these objects at once. But our table can only hold 20 cells per screen and we only need to display 20 objects. In this case it would be a good idea to set fetchBatchSize to 20. At first, Core Data will request 20 objects to display in our table from the database and will then request the next batch of 20 entries on a scroll. This approach is sure to help you optimize your persistent store interaction.

Make sure that your batch size is not too small, though. A batch size of 1 will only bog down your database with constant requests for a single entry.

Objective-C

fetchRequest.fetchBatchSize = 20;

Swift

fetchRequest.fetchBatchZize = 20

resultType

Objective-C

@property (nonatomic) NSFetchRequestResultType resultType

Swift

<pre>
var resultType: NSFetchRequestResultType


When fetching data, the executeFetchRequest(_:) method by default returns the array of objects of the NSManagedObject class and its children.

Changing the resultType property allows you to pick the type of fetched objects. Let’s take a look at them (Objective-C with the NS prefix is followed by Swift after a slash):


  • NSManagedObjectResultType / ManagedObjectResultType — objects of the NSManagedObject class and its children (by default).
  • NSManagedObjectIDResultType / ManagedObjectIDResultType — identifier of NSManagedObject.
  • NSDictionaryResultType / DictionaryResultType — dictionary with keys that are entity attribute names.
  • NSCountResultType / CountResultType — will return one array entry with a value of entry count.

propertiesToFetch

Objective-C

@property (nonatomic, copy) NSArray *propertiesToFetch

Swift

var propertiesToFetch: [AnyObject]?


This property allows you to fetch only the required attributes from the entity, but only under the condition that resultType is a dictionary (NSDictionaryResultType / DictionaryResultType).


In-use example

As an example, we’ll fetch only the values of the name attribute, and for output we’ll print out all the values for all the existing dictionary keys in the following format: (key: value).

Objective-C

fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = @[@"name"];

Swift

fetchRequest.resultType = .DictionaryResultType
fetchRequest.propertiesToFetch = ["name"]

The result (key: value)

name: Donut
name: Soda
name: Soda
name: Cheddar Cheese
name: Flour
name: Parmesan Cheese
name: Soda
name: Milk
name: Soda
name: Parmesan Cheese
name: Donut
name: Soda
name: Banana
name: Parmesan Cheese
name: Flour
name: Flour
name: Cheddar Cheese
name: Flour
name: Parmesan Cheese
name: Donut
name: Banana
name: Soda
name: Parmesan Cheese
name: Banana
name: Donut
name: Milk
name: Banana
name: Milk
name: Soda
name: Parmesan Cheese

includesSubentities

Objective-C

@property (nonatomic) BOOL includesSubentities

Swift

var includesSubentities: Bool

To demonstrate this property we’ll have to add an object into the FavoriteProducts entity that is inherited from Products. We’ll assign this object the name "Apple" and a price of 999.

We’ll request the Products entity that we used to sort by name and price.

The result

NAME: Banana, PRICE: 185
NAME: Banana, PRICE: 334
NAME: Banana, PRICE: 731
NAME: Banana, PRICE: 972
NAME: Donut, PRICE: 156
NAME: Donut, PRICE: 492
NAME: Donut, PRICE: 701
NAME: Donut, PRICE: 821
NAME: Soda, PRICE: 76
NAME: Soda, PRICE: 85
NAME: Soda, PRICE: 176
NAME: Soda, PRICE: 425
NAME: Soda, PRICE: 448
NAME: Soda, PRICE: 539
NAME: Soda, PRICE: 635
NAME: Parmesan Cheese, PRICE: 345
NAME: Parmesan Cheese, PRICE: 646
NAME: Parmesan Cheese, PRICE: 718
NAME: Parmesan Cheese, PRICE: 746
NAME: Parmesan Cheese, PRICE: 861
NAME: Parmesan Cheese, PRICE: 872
NAME: Cheddar Cheese, PRICE: 221
NAME: Cheddar Cheese, PRICE: 400
NAME: Milk, PRICE: 409
NAME: Milk, PRICE: 633
NAME: Milk, PRICE: 734
NAME: Flour, PRICE: 456
NAME: Flour, PRICE: 519
NAME: Flour, PRICE: 560
NAME: Flour, PRICE: 920
NAME: Apple, PRICE: 999

Take note of the object that we’ve just added for the FavoriteProducts entity at the end of the list. Why is it there? By default, the request’s  includesSubentities property value is equal to Boolean true, which leads to fetching of objects of both the current entity and children entities.

To prevent this, change it to Boolean false.

Objective-C

fetchRequest.includesSubentities = NO;

Swift

fetchRequest.includesSubentities = false

The result

NAME: Banana, PRICE: 185
NAME: Banana, PRICE: 334
NAME: Banana, PRICE: 731
NAME: Banana, PRICE: 972
NAME: Donut, PRICE: 156
NAME: Donut, PRICE: 492
NAME: Donut, PRICE: 701
NAME: Donut, PRICE: 821
NAME: Soda, PRICE: 76
NAME: Soda, PRICE: 85
NAME: Soda, PRICE: 176
NAME: Soda, PRICE: 425
NAME: Soda, PRICE: 448
NAME: Soda, PRICE: 539
NAME: Soda, PRICE: 635
NAME: Parmesan Cheese, PRICE: 345
NAME: Parmesan Cheese, PRICE: 646
NAME: Parmesan Cheese, PRICE: 718
NAME: Parmesan Cheese, PRICE: 746
NAME: Parmesan Cheese, PRICE: 861
NAME: Parmesan Cheese, PRICE: 872
NAME: Cheddar Cheese, PRICE: 221
NAME: Cheddar Cheese, PRICE: 400
NAME: Milk, PRICE: 409
NAME: Milk, PRICE: 633
NAME: Milk, PRICE: 734
NAME: Flour, PRICE: 456
NAME: Flour, PRICE: 519
NAME: Flour, PRICE: 560
NAME: Flour, PRICE: 920

Fetched Results Controller (FRC)

The NSFetchedResultsController class controller can be placed between Core Data and ViewController, in which we need to display data from our database. The methods and properties of this controller allow for convenient interaction, display and management of objects from Core Data together with UITableView tables, for which it’s best adapted.

This controller can convert fetched objects into table entries (sections and objects of these sections). FRC has the NSFetchedResultsControllerDelegate protocol that allows tracking changes that happen to the objects of the specified NSFetchRequest on controller initialization.


FRC initialization

Objective-C

- (instancetype)initWithFetchRequest:(NSFetchRequest *)fetchRequest managedObjectContext: (NSManagedObjectContext *)context sectionNameKeyPath:(nullable NSString *)sectionNameKeyPath cacheName:(nullable NSString *)name;

Swift

public init(fetchRequest: NSFetchRequest, managedObjectContext context: NSManagedObjectContext, sectionNameKeyPath: String?, cacheName name: String?)

Let’s examine the initialization parameters:


  • fetchRequest — request for NSFetchRequest object fetch. Note: for FRC to work, the request needs to have at least one sort descriptor and its resultType must be NSManagedObjectResultType / ManagedObjectResultType.
  • context — the NSManagedObjectContext context in which we are working.
  • sectionNameKeyPath — an optional parameter. When provided in a string key format (entity attribute name), it groups objects with equal values of this attribute in a table section. It is important that this key matches the sort descriptor with the highest priority. If this parameter is not provided, a table with one section will be created.
  • cacheName — an optional parameter. When it is provided, the controller starts to cache request results; we’ll take a closer look at it later.

Our next step will be to invoke the performFetch controller method to actually execute the fetch.

Objective-C

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

Swift

do {
    try fetchedResultsController.performFetch()
} catch {
    print(error)
}

The method returns a Boolean value. A Boolean true is returned in case of a successful fetch, otherwise it’s false. After the objects are fetched, they are placed in the fetchedObjects controller property.


Interaction with UITableView

t’s time to take a look at interaction with tables. Even though fetched objects are placed in the fetchedObject property, to actually work with them you have to use the sections controller property. It’s an object array under the NSFetchedResultsSectionInfo protocol that defines the following properties:


  • name — section name.
  • indexTitle — section title.
  • numbersOfObjects — the number of objects in a section.
  • objects — the actual object array located in the section.

Implementation

For our convenience, we’ll add the configureCell table cell configuration method.

Objective-C

#pragma mark - Table View
- (void)configureCell:(UITableViewCell *)cell withObject:(Products *)object {
    cell.textLabel.text = object.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", object.price.intValue];
}
#pragma mark UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[self.fetchedResultsController sections] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
    return sectionInfo.indexTitle;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
    return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *identifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
    }
    Products *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    [self configureCell:cell withObject:(Products *)object];
    return cell;
}

Swift

// MARK: - Table View
extension ViewController {
    func configureCell(cell: UITableViewCell, withObject product: Products) {
        cell.textLabel?.text = product.name ?? ""
        cell.detailTextLabel?.text = String(product.price ?? 0)
    }
}
// MARK: UITableViewDataSource
extension ViewController: UITableViewDataSource {
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        guard let sections = fetchedResultsController.sections else { return 0 }
        return sections.count
    }
    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        guard let sections = fetchedResultsController.sections else { return nil }
        return sections[section].indexTitle ?? ""
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let sections = fetchedResultsController.sections else { return 0 }
        return sections[section].numberOfObjects
    }
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let identifier = "Cell"
        let product = fetchedResultsController.objectAtIndexPath(indexPath) as! Products
        var cell = tableView.dequeueReusableCellWithIdentifier(identifier)
        if cell == nil { cell = UITableViewCell(style: .Value1, reuseIdentifier: identifier) }
        configureCell(cell!, withObject: product)
        return cell!
    }
}

Using NSFetchRequest with sorting by name and price and specifying sectionNameKeyPath for FRC as "name" attribute name, we get a table that groups our grocery products by name.

The result

NSFetchRequest_2_en.gif#asset:7286



FRC can function in several modes:

  • The controller doesn’t have a delegate and no cache name is specified (delegate = nil, cacheName = nil) — in this mode, the data is retrieved only on fetch request and is not cached.
  • The delegate is assigned but there’s no cache name (delegate != nil, cacheName = nil) — change tracking mode that uses the NSFetchedResultsControllerDelegate protocol method (we’ll describe it later). Still no object caching.
  • Both delegate and cache name are set (delegate != nil, cacheName = <#NSString/String#>) — full tracking mode with object caching.


NSFetchedResultsControllerDelegate

NSFetchedResultsControllerDelegate provides mechanisms for tracking changes that happen to the objects of our NSFetchRequest in the model. Let’s refer to the UITableView example to learn how to display changes that have taken place in the model while maintaining proper UI representation.

Objective-C

#pragma mark - NSFetchedResultsControllerDelegate
// 1
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
    return sectionName;
}
// 2
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}
// 3
- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type {
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        default:
            return;
    }
}
// 4
- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = self.tableView;
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] withObject:anObject];
            break;
        case NSFetchedResultsChangeMove:
            [tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
            break;
    }
}
// 5
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
}

Swift

// MARK: - NSFetchedResultsControllerDelegate
extension ViewController: NSFetchedResultsControllerDelegate {
    // 1
    func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
        return sectionName
    }
    // 2
    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        tableView.beginUpdates()
    }
    // 3
    func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
        switch type {
        case .Insert:
            tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        case .Delete:
            tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        default:
            return
        }
    }
    // 4
    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Insert:
            if let indexPath = newIndexPath {
                tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
            }
        case .Update:
            if let indexPath = indexPath {
                let product = fetchedResultsController.objectAtIndexPath(indexPath) as! Products
                guard let cell = tableView.cellForRowAtIndexPath(indexPath) else { break }
                configureCell(cell, withObject: product)
            }
        case .Move:
            if let indexPath = indexPath {
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
            }
            if let newIndexPath = newIndexPath {
                tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic)
            }
        case .Delete:
            if let indexPath = indexPath {
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
            }
        }
    }
    // 5
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        tableView.endUpdates()
    }
}


Delegate methods and their usage:

  1. sectionIndexTitleForSectionName — by default, section names obtain their values from the value of the attribute that is used for object grouping. By implementing this method we can change the name, altering the default value (sectionName argument) or writing in an entirely different value. The returned string is the new value. We’ll just return the default value.

  2. controllerWillChangeContent — the method notifies the delegate that the controller is about to start processing one or more changes by request that our controller works with. In it, we’ll invoke the UITableView table method — beginUpdates.

  3. didChangeSection — the delegate tracks the method for data changes in the model that lead to changes in sections. The method accepts the following arguments: sectionInfo — describes a section that was changed, sectionIndex — the section’s index and type NSFetchedResultsChangeType that describes the change type (Insert, Delete, Move, Update). In this method we’ll describe animated section addition and deletion.

  4. didChangeObject — this method is similar to the previous one, but instead of the sectionInfo argument that describes the section, there’s the anObject argument that is a modifiable object, and instead of sectionIndex there is the old index that the object had before the changes — indexPath, and newIndexPath that it obtained after the changes. Using UITableView** methods, we’ll handle animated object addition, deletion, movement and updating.

  5. controllerDidChangeContent — the method notifies the delegate that the changes have taken place. We’ll call the endUpdates table method in it.

For display purposes, let’s add two new objects: “Banana" and "Cheddar Cheese", both with a price of 1.

The result


NSFetchRequest_2_en.gif#asset:7286


The Cache

As for caching, the controller can cache the objects to avoid repeating the same data request tasks. It is rational to use caching for requests that are not changed during the application’s work. If it’s necessary to change the request, we’ll just call the (deleteCacheWithName:) method to prevent errors when using the same cache for different requests. The requests are cached to the Core Data file with the name that is assigned in cacheName on controller initialization.

How does cache work?

  • If no cache is found by the assigned cacheName, the controller calculates the required section and object information and writes it to disk.
  • If there is a cache, the controller verifies whether it’s valid (checks the entity name, hash version, section key-path and sort descriptors). If the cache is valid, it’s used; otherwise, the controller updates the cache.


Summary

The properties of the NSFetchRequest class described in this article, together with their in-use examples, demonstrate that the request functionality presented by the Core Data framework is very flexible and can be used to efficiently fetch data from your app’s database. The Fetched Results Controller tool allows you to track changes in your model’s objects and easily converts fetched data into UITableView entries.

iOS
Development
Core Data Framework