Hello! This article is aimed at developers who have a minimal skill of working with the Core Data Framework
. Let me remind you that Core Data
is a framework for storing data on the device and interacting with them. On this subject there is a bunch of Russian-language articles on Habré and the network, so I do not see the need to repeat their content.
Often beginners especially stack overflow developers are afraid to use the Core Data Framework
, because it seems difficult for them, or use only a small part of its capabilities. In reality, knowledge of the basic functions of the classes of this framework allows the developer to work with the model with convenience.
In the article I want to focus on the following points:
NSFetchRequest
class, which is used to create requests to extract data from a model. We will study its basic properties and cases with their application;NSFetchedResultsController
in detail the functions and operation of NSFetchedResultsController
by effectively presenting the extracted data using NSFetchRequest
using the example of UITableView
.The demo project, on which we will “experiment”, is very primitive. It includes the Model
and ViewController
, in which the UITableView
is located.
For specifics, we will use a banal list of products with the name and price.
The model will contain two entities: Products
with the attributes name
and price
and FavoriteProducts
, which inherits these attributes.
For example, let's fill our database with a number of products with random price (up to 1000
) and the name of a product from the list: ””, “”, “”, “”, “ «»”, “ «»”, “”, “”
.
In the controller, with the help of the code, we initialize and place the table in full screen.
- (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; }
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 }
Extraction of data from the model is performed by the NSManagedObjectContext
method executeFetchRequest(_:)
. The method argument is the sampling request. NSFetchRequest
is the main character of this article.
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];
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)") }
The return type of the executeFetchRequest(_:)
method is an array of objects of the NSManagedObject
class by default. For clarity, print the elements extracted from our model, converting the output.
NAME: , PRICE: 156 NAME: , PRICE: 425 NAME: , PRICE: 85 NAME: «», PRICE: 400 NAME: , PRICE: 920 NAME: «», PRICE: 861 NAME: , PRICE: 76 NAME: , PRICE: 633 NAME: , PRICE: 635 NAME: «», PRICE: 718 NAME: , PRICE: 701 NAME: , PRICE: 176 NAME: , PRICE: 731 NAME: «», PRICE: 746 NAME: , PRICE: 456 NAME: , PRICE: 519 NAME: «», PRICE: 221 NAME: , PRICE: 560 NAME: «», PRICE: 646 NAME: , PRICE: 492 NAME: , PRICE: 185 NAME: , PRICE: 539 NAME: «», PRICE: 872 NAME: , PRICE: 972 NAME: , PRICE: 821 NAME: , PRICE: 409 NAME: , PRICE: 334 NAME: , PRICE: 734 NAME: , PRICE: 448 NAME: «», PRICE: 345
As I said above, the NSFetchRequest
class NSFetchRequest
used as a request to fetch data from a model. This tool allows you to set the rules for filtering and sorting objects at the stage of retrieving them from the database. This operation becomes many times more efficient and more productive than if we first performed the extraction of all objects (and if there are 10,000 or more of them?), And then manually sorted or filtered the data of interest to us.
Knowing the basic properties of this class, you can easily handle queries and get a specific sample without developing additional algorithms and crutches - everything is already implemented in Core Data
. Let's get started
@property (nonatomic, strong) NSArray <NSSortDescriptor *> *sortDescriptors
var sortDescriptors: [NSSortDescriptor]?
I want to start a review of class properties with sortDesctriptors
, which is an array of objects of the NSSortDescriptor
class. It is through them that the sorting mechanism is implemented. Instructions on using sorting descriptors can be found on the Apple portal. This property accepts an array of sort descriptors, which allows us to use several sorting rules. The priorities for this use are equivalent to the First In-First Out (FIFO) rules: the smaller the index by which the object is located in the array, the higher the sorting priority.
The output of all the objects that we reviewed earlier is chaotic and not very readable. For convenience, we sort this list first by product name, specify the name of the name attribute with a sort key, and then sort by price. We want to sort the names in alphabetical order, and the price in ascending order. To do this, we set the ascending
value of both predicates to boolean true
(the boolean false
used for descending sorting).
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; NSSortDescriptor *priceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"price" ascending:YES]; fetchRequest.sortDescriptors = @[nameSortDescriptor, priceSortDescriptor];
let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true) let priceSortDescriptor = NSSortDescriptor(key: "price", ascending: true) fetchRequest.sortDescriptors = [nameSortDescriptor, priceSortDescriptor]
NAME: , PRICE: 185 NAME: , PRICE: 334 NAME: , PRICE: 731 NAME: , PRICE: 972 NAME: , PRICE: 156 NAME: , PRICE: 492 NAME: , PRICE: 701 NAME: , PRICE: 821 NAME: , PRICE: 76 NAME: , PRICE: 85 NAME: , PRICE: 176 NAME: , PRICE: 425 NAME: , PRICE: 448 NAME: , PRICE: 539 NAME: , PRICE: 635 NAME: «», PRICE: 345 NAME: «», PRICE: 646 NAME: «», PRICE: 718 NAME: «», PRICE: 746 NAME: «», PRICE: 861 NAME: «», PRICE: 872 NAME: «», PRICE: 221 NAME: «», PRICE: 400 NAME: , PRICE: 409 NAME: , PRICE: 633 NAME: , PRICE: 734 NAME: , PRICE: 456 NAME: , PRICE: 519 NAME: , PRICE: 560 NAME: , PRICE: 920
@property (nonatomic, strong) NSPredicate *predicate
var predicate: NSPredicate?
The next property under consideration is the NSPredicate
class NSPredicate
, which is a powerful and fast data filtering tool. There is an excellent Apple Guide ( Translation ) for using a predicate. Filtering data in the request is due to the special string syntax of the predicate, which is described in the above guide.
Let's start with a simple example: we are passionate lovers of milk sausage and want to find out the prices for it, presented in the list of products. To do this, we indicate in the predicate that we want to get objects whose name attribute name
is equal to the string " «»"
.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", @" «»"]; fetchRequest.predicate = predicate;
let predicate = NSPredicate(format: "name == %@", " «»") fetchRequest.predicate = predicate
NAME: «», PRICE: 400 NAME: «», PRICE: 221
Note that in order to correctly compile a predicate using the equality operator ==
it is necessary to accurately specify the string value, taking into account the register.
And if we want to see the prices of not only dairy, but all kinds of sausages? To do this, we turn to the CONTAINS
operator (the left expression right expression) and add the keywords
[cd]
, which indicate case insensitivity to accented characters. We can also use several conditions in which the AND
operator will help us. We limit the results by cost - up to 500 monetary units.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@ AND price < %d", @"", 500]; fetchRequest.predicate = predicate;
let predicate = NSPredicate(format: "name CONTAINS[cd] %@ AND price < %d", "", 500) fetchRequest.predicate = predicate
NAME: «», PRICE: 400 NAME: «», PRICE: 221 NAME: «», PRICE: 345
@property (nonatomic) NSUInteger fetchLimit
var fetchLimit: Int
The fetchLimit
property allows fetchLimit
to limit the number of objects to be extracted.
For the demonstration, we get 12 of the cheapest products from the list of products. To do this, add a sort by price and the limit on the number of extracted objects - 12
.
// NSSortDescriptor *priceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"price" ascending:YES]; fetchRequest.sortDescriptors = @[priceSortDescriptor]; // = 12 fetchRequest.fetchLimit = 12;
// let priceSortDescriptor = NSSortDescriptor(key: "price", ascending: true) fetchRequest.sortDescriptors = [priceSortDescriptor] // = 12 fetchRequest.fetchLimit = 12
NAME: , PRICE: 76 NAME: , PRICE: 85 NAME: , PRICE: 156 NAME: , PRICE: 176 NAME: , PRICE: 185 NAME: «», PRICE: 221 NAME: , PRICE: 334 NAME: «», PRICE: 345 NAME: «», PRICE: 400 NAME: , PRICE: 409 NAME: , PRICE: 425 NAME: , PRICE: 448
@property (nonatomic) NSUInteger fetchOffset
var fetchOffset: Int
With this property you can shift the results of the sample to a specified number of objects.
To show the operation of this property, we use the previous query and add an offset to two objects to it. As a result, we get 12 objects, where the first two are missing, and the following are added to the end, as if we had shifted the table with the results by two cells.
fetchRequest.fetchOffset = 2;
fetchRequest.fetchOffset = 2
For clarity, I have limited the extracted objects to a dotted line.
: : NAME: , PRICE: 76 NAME: , PRICE: 85 --------------------------------------------------------------------------------- NAME: , PRICE: 76 NAME: , PRICE: 156 NAME: , PRICE: 85 NAME: , PRICE: 176 NAME: , PRICE: 156 NAME: , PRICE: 185 NAME: , PRICE: 176 NAME: «», PRICE: 221 NAME: , PRICE: 185 NAME: , PRICE: 334 NAME: «», PRICE: 221 NAME: «», PRICE: 345 NAME: , PRICE: 334 NAME: «», PRICE: 400 NAME: «», PRICE: 345 NAME: , PRICE: 409 NAME: «», PRICE: 400 NAME: , PRICE: 425 NAME: , PRICE: 409 NAME: , PRICE: 448 NAME: , PRICE: 425 NAME: , PRICE: 456 NAME: , PRICE: 448 NAME: , PRICE: 492 --------------------------------------------------------------------------------- NAME: , PRICE: 456 ... NAME: , PRICE: 492 ...
@property (nonatomic) NSUInteger fetchBatchSize
var fetchBatchSize: Int
With the help of fetchBatchSize
regulated how many objects at a time will be retrieved from the database ( Persistent Store
) with which the Core Data Framework
works ( SQLite
, XML
, etc.). Correctly established value for specific cases can both speed up work with the base and, conversely, slow down.
Suppose we work with a UITableView
. There are more than 10,000 objects in our model. It will take some time to extract all these items at once. But the table we have holds 20 cells per screen, and for display we will need only 20 objects. In such a case, it is advisable to use fetchBatchSize
equal to 20. First, Core Data
will query the database for 20 objects, which we will display in the table, and when scrolling, the next pack of 20 elements will be requested. This approach greatly optimizes the interaction with persistent storage.
But do not use too small a pack size, for example, equal to 1 - this will only load the database with constant requests for one element.
fetchRequest.fetchBatchSize = 20;
fetchRequest.fetchBatchZize = 20
@property (nonatomic) NSFetchRequestResultType resultType
var resultType: NSFetchRequestResultType
When retrieving data, the executeFetchRequest(_:)
method by default returns an array of NSManagedObject
objects and its heirs.
Changing the resultType
property allows resultType
to select the type of objects you extracted. Consider them (Objective-C with the prefix NS alternates with Swift through a slash):
NSManagedObjectResultType
/ ManagedObjectResultType
- objects of the NSManagedObject
class and its successors (by default).NSManagedObjectIDResultType
/ ManagedObjectIDResultType
- NSManagedObject
object identifier.NSDictionaryResultType
/ DictionaryResultType
is a dictionary where the keys are the attribute names of the entity.NSCountResultType
/ CountResultType
- returns one array element with the value of the number of elements. @property (nonatomic, copy) NSArray *propertiesToFetch
var propertiesToFetch: [AnyObject]?
This property allows you to extract from the entity only the necessary attributes. But the prerequisite is that resultType
must be a dictionary ( NSDictionaryResultType
/ DictionaryResultType
).
As an example, we extract only the values of the name
attribute and, for output, we print out all the values for all existing dictionary keys in the format (key: value)
.
fetchRequest.resultType = NSDictionaryResultType; fetchRequest.propertiesToFetch = @[@"name"];
fetchRequest.resultType = .DictionaryResultType fetchRequest.propertiesToFetch = ["name"]
name: name: name: name: «» name: name: «» name: name: name: name: «» name: name: name: name: «» name: name: name: name: name: «» name: name: name: name: «» name: name: name: name: name: name: name: «»
@property (nonatomic) BOOL includesSubentities
var includesSubentities: Bool
To demonstrate this property, you need to add an object to the FavoriteProducts
entity, which is a successor of Products
. Give this object the name ""
and price 999
.
Referring to the request for the Products
entity, with which we sorted by name and price.
NAME: , PRICE: 185 NAME: , PRICE: 334 NAME: , PRICE: 731 NAME: , PRICE: 972 NAME: , PRICE: 156 NAME: , PRICE: 492 NAME: , PRICE: 701 NAME: , PRICE: 821 NAME: , PRICE: 76 NAME: , PRICE: 85 NAME: , PRICE: 176 NAME: , PRICE: 425 NAME: , PRICE: 448 NAME: , PRICE: 539 NAME: , PRICE: 635 NAME: «», PRICE: 345 NAME: «», PRICE: 646 NAME: «», PRICE: 718 NAME: «», PRICE: 746 NAME: «», PRICE: 861 NAME: «», PRICE: 872 NAME: «», PRICE: 221 NAME: «», PRICE: 400 NAME: , PRICE: 409 NAME: , PRICE: 633 NAME: , PRICE: 734 NAME: , PRICE: 456 NAME: , PRICE: 519 NAME: , PRICE: 560 NAME: , PRICE: 920 NAME: , PRICE: 999
At the end of the list, notice the object we just added for the FavoriteProducts
entity. What is he doing here? The point is that the default value of the includesSubentities
property is equal to the boolean true
by default, which means retrieving objects not only of the current entity, but also of successor entities.
To avoid this, change it to boolean false
.
fetchRequest.includesSubentities = NO;
fetchRequest.includesSubentities = false
NAME: , PRICE: 185 NAME: , PRICE: 334 NAME: , PRICE: 731 NAME: , PRICE: 972 NAME: , PRICE: 156 NAME: , PRICE: 492 NAME: , PRICE: 701 NAME: , PRICE: 821 NAME: , PRICE: 76 NAME: , PRICE: 85 NAME: , PRICE: 176 NAME: , PRICE: 425 NAME: , PRICE: 448 NAME: , PRICE: 539 NAME: , PRICE: 635 NAME: «», PRICE: 345 NAME: «», PRICE: 646 NAME: «», PRICE: 718 NAME: «», PRICE: 746 NAME: «», PRICE: 861 NAME: «», PRICE: 872 NAME: «», PRICE: 221 NAME: «», PRICE: 400 NAME: , PRICE: 409 NAME: , PRICE: 633 NAME: , PRICE: 734 NAME: , PRICE: 456 NAME: , PRICE: 519 NAME: , PRICE: 560 NAME: , PRICE: 920
The NSFetchedResultsController
class NSFetchedResultsController
can be conditionally positioned between Core Data
and ViewController
, in which we need to display data from the database. The methods and properties of this controller allow you to conveniently interact, present and manage objects from Core Data
in conjunction with the UITableView
tables for which it is most adapted.
This controller can convert extracted objects into table elements - sections and objects of these sections. FRC
has the NSFetchedResultsControllerDelegate
protocol, which, when delegated, allows you to detect changes that occur with objects of a given NSFetchRequest
request at the time of controller initialization.
- (instancetype)initWithFetchRequest:(NSFetchRequest *)fetchRequest managedObjectContext: (NSManagedObjectContext *)context sectionNameKeyPath:(nullable NSString *)sectionNameKeyPath cacheName:(nullable NSString *)name;
public init(fetchRequest: NSFetchRequest, managedObjectContext context: NSManagedObjectContext, sectionNameKeyPath: String?, cacheName name: String?)
Let's sort the initialization parameters:
fetchRequest
- request to retrieve NSFetchRequest
objects. Important: for the FRC
to work, the request must have at least one sort descriptor and its resultType
must be NSManagedObjectResultType / ManagedObjectResultType
.context
- the NSManagedObjectContext
context in which we work.sectionNameKeyPath
is an optional parameter. If you specify it in the format of a string key (entity attribute name), objects are grouped with the same values of this attribute in a table section. It is important that this key matches the sort descriptor, which has the highest priority. If you do not specify this parameter, a table with one section will be created.cacheName
is an optional parameter, specifying which the controller starts caching query results. Consider it later in more detail.The next step is to call the controller's performFetch
method in order to extract the sample from the database.
NSError *error = nil; if (![self.fetchedResultsController performFetch:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); }
do { try fetchedResultsController.performFetch() } catch { print(error) }
The method returns a boolean value. If the extraction is successful, the boolean true
will return, otherwise false
. After extraction, the objects are in the controller's fetchedObjects
.
Consider working with the table. Although the extracted objects are in the fetchedObject
property, to work with them, you should refer to the sections
property property. This is an array of objects that subscribe to the NSFetchedResultsSectionInfo
protocol, which describes the following properties:
name
- the section name.indexTitle
- section header.numbersOfObjects
- the number of objects in the section.objects
- the array of objects themselves, located in the section.To make it convenient for us, we will add a method to configure the cell of the configureCell
table.
#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; }
// 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! } }
NSFetchRequest
sectionNameKeyPath
FRC
"name"
, .
FRC :
(delegate = nil, cacheName = nil)
— .(delegate != nil, cacheName = nil)
— NSFetchedResultsControllerDelegate
, . .(delegate != nil, cacheName = <#NSString/String#>)
— .NSFetchedResultsControllerDelegate
, , NSFetchRequest
. UITableView
, UI- , .
#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]; }
// 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() } }
sectionIndexTitleForSectionName
— , . , , ( sectionName
) . . .
controllerWillChangeContent
— , . UITableView
— beginUpdates
.
didChangeSection
— , , . : sectionInfo
— , , sectionIndex
— type
NSFetchedResultsChangeType
, (Insert, Delete, Move, Update). .
didChangeObject
— , , sectionInfo
, anObject
, , sectionIndex
— , indexPath
newIndexPath
, . UITableView
, , , , .
controllerDidChangeContent
— . endUpdates
.— ""
""
1
.
. . . (deleteCacheWithName:)
, . Core Data , cacheName
.
?
cacheName
, .NSFetchRequest
, Core Data
, . Fetched Results Controller
, , UITableView
.
, , .
Source: https://habr.com/ru/post/309624/
All Articles