An Interest In:
Web News this Week
- April 29, 2024
- April 28, 2024
- April 27, 2024
- April 26, 2024
- April 25, 2024
- April 24, 2024
- April 23, 2024
Core Data and Swift: Batch Updates
Even though Core Data has been around for many years on OS X and iOS, a feature that was added only recently are batch updates. Developers have been asking for this feature for many years and Apple finally found a way to integrate it into Core Data. In this tutorial, I will show you how batch updates work and what they mean for the Core Data framework.
1. The Problem
Core Data is great at managing object graphs. Even complex object graphs with many entities and relationships aren't much of a problem for Core Data. However, Core Data does have a few weak spots, updating large numbers of records being one of them.
The problem is easy to understand. Whenever you update a record, Core Data loads the record into memory, updates the record, and saves the changes to the persistent store, a SQLite database for example.
If Core Data needs to update a large number of records, it needs to load every record into memory, update the record, and send the changes to the persistent store. If the number of records is too large, iOS will simply bail out due to a lack of resources. Even though a device running OS X may have the resources to execute the request, it will be slow and consume a lot of memory.
An alternative approach is to update the records in batches, but that too takes a lot of time and resources. On iOS 7, it's the only option iOS developers have. That's no longer the case since iOS 8 and OS X Yosemite.
2. The Solution
On iOS 8 and up and OS X Yosemite and up, it's possible to talk directly to the persistent store and tell it what you'd like to change. This generally involves updating an attribute. Apple calls this feature batch updates.
There are a number of pitfalls to watch out for though. Core Data does a lot of things for you and you may not even realize it until you use batch updates. Validation is a good example. Because Core Data performs batch updates directly on the persistent store, such as a SQLite database, Core Data isn't able to perform any validation on the data you write to the persistent store. This means that you are in charge of making sure you don't add invalid data to the persistent store.
When would you use batch updates? Apple recommends to only use this feature if the traditional approach is too resource or time intensive. If you need to mark hundreds or thousands of email messages as read, then batch updates is the best solution on iOS 8 and up and OS X Yosemite and up.
3. How Does It Work?
To illustrate how batch updates work, I suggest we revisit Done, a simple Core Data application that manages a to-do list. We'll add a button to the navigation bar that marks every item in the list as done.
Step 1: Projet Setup
Download or clone the project from GitHub and open it in Xcode 7. Run the application in the simulator and add a few to-do items.
Step 2: Create Bar Button Item
Open ViewController.swift and declare a property, checkAllButton
, of type UIBarButtonItem
at the top.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
var checkAllButton: UIBarButtonItem!
...
}
Initialize the bar button item in the viewDidLoad()
method of the ViewController
class and set it as the left bar button item of the navigation item.
// Initialize Check All Button
checkAllButton = UIBarButtonItem(title: "Check All", style: .Plain, target: self, action: "checkAll:")
// Configure Navigation Item
navigationItem.leftBarButtonItem = checkAllButton
Step 3: Implement checkAll(_:)
Method
The checkAll(_:)
method is fairly easy, but there are a few caveats to watch out for. Take a look at its implementation below.
func checkAll(sender: UIBarButtonItem) {
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
}
Create Batch Request
We start by creating an NSEntityDescription
instance for the Item entity and use it to initialize an NSBatchUpdateRequest
object. The NSBatchUpdateRequest
class is a subclass of NSPersistentStoreRequest
.
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
We set the result type of the batch update request to .UpdatedObjectIDsResultType
, a member value of the NSBatchUpdateRequestResultType
enum. This means that the result of the batch update request will be an array containing the object IDs, instances of the NSManagedObjectID
class, of the records that were changed by the batch update request.
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
We also populate the propertiesToUpdate
property of the batch update request. For this example, we set propertiesToUpdate
to a dictionary containing one key, "done"
, with value NSNumber(bool: true)
. This means that every Item record will be set to done, which is what we're after.
// Configure Batch Update Request
batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
Execute Batch Update Request
Even though batch updates bypass the managed object context, executing a batch update request is done by calling executeRequest(_:)
on an NSManagedObjectContext
instance. This method accepts one argument, an instance of the NSPersistentStoreRequest
class. Because executeRequest(_:)
is a throwing method, we execute the method in a do-catch
statement.
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
Updating the Managed Object Context
Even though we hand the batch update request to a managed object context, the managed object context is not aware of the changes as a result of executing the batch update request. As I mentioned earlier, it bypasses the managed object context. This gives batch updates their power and speed. To remedy this issue, we need to do two things:
- turn the managed objects that were updated by the batch update into faults
- tell the fetched results controller to perform a fetch to update the user interface
This is what we do in the next few lines of the checkAll(_:)
method in the do
clause of the do-catch
statement. If the batch update request is successful, we extract the array of NSManagedObjectID
instances from the NSBatchUpdateResult
object.
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
We then iterate over the objectIDs
array, ask the managed object context for the corresponding NSManagedObject
instance, and turn it into a fault by invoking refreshObject(_:mergeChanges:)
, passing in the managed object as the first argument. To force the managed object into a fault, we pass false
as the second argument.
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
Fetching Updated Records
The last step is to tell the fetched results controller to perform a fetch to update the user interface. If this is unsuccessful, we catch the error in the catch
clause of the do-catch
statement.
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
While this may seem cumbersome and fairly complex for an easy operation, keep in mind that we bypass the Core Data stack. In other words, we need to take care of some housekeeping that's usually done for us by Core Data.
Step 4: Build & Run
Build the project and run the application in the simulator or on a physical device. Click or tap the bar button item on the right to check every to-do item in the list.
Conclusion
Batch updates are a great addition to the Core Data framework. Not only does it answer a need developers have had for many years, it isn't difficult to implement as long as you keep a few basic rules in mind. In the next tutorial, we'll take a closer look at batch deletes, another feature of the Core Data framework that was added only recently.
Original Link:
TutsPlus - Code
Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.More About this Source Visit TutsPlus - Code