Working with Core Data and iCloud - Using iCloud Documents and Data - Learning iCloud Data Management (2014)

Learning iCloud Data Management (2014)

Part IV: Using iCloud Documents and Data

17. Working with Core Data and iCloud

In Chapter 10, “Managing Persistent Storage with Core Data,” you saw the basics of Core Data and an overview of relevant database theory. Throughout Part IV of the book, you have seen how to use iCloud in various contexts—with file wrappers (Chapter 14), iOS documents (Chapter 15), and OS X documents (Chapter 16). Core Data can fit into any of those contexts.

For example, you can use a file wrapper to wrap a file that contains a Core Data store and the necessary code for the Core Data stack to access it. You can add the store and Core Data stack support to a subclass of UIDocument (it is called UIManagedDocument). On OS X,NSPersistentDocument is a subclass of NSDocument and is functionally comparable to UIManagedDocument.

For more information on Core Data itself, see Jesse Feiler’s Sams Teach Yourself Core Data in 24 Hours, Second Edition.

Looking at the iCloud Core Data Implementation

The basic structure of Core Data needed very little changing to support iCloud. The changes that have been made build on the existing structure. However, it is very important to note that those changes came in two batches. The initial release of iCloud and Core Data was for iOS 6 and OS X Mountain Lion (10.8), both of which were announced at Apple’s Worldwide Developers Conference in 2012. The iCloud integration was based on a transactional model (it is described shortly). Over the course of a year, people worked on apps that used iCloud and Core Data; some problems were encountered. In a review of new features at WWDC 2013, it was announced that the Core Data team had devoted the year to strengthening the iCloud implementation and making some revisions (mostly simplifications) to the way it works under the hood.

Also, remember that Core Data and iCloud themselves are a collection of modifications to various frameworks from the addition of new notifications, a few new messages on the basic Core Data classes, and more. For that reason, this chapter may appear to jump around a bit, but that is the nature of the iCloud/Core Data implementation.

Using the Class Extension for the Snippets in This Chapter

The changes that were made to iCloud at WWDC 2013 are significant enough that it’s very important to check the dates on any materials that provide Core Data information. Information dated after June 2013 that incorporates the WWDC 2013 revisions is what you should be looking for. (That’s the information that’s provided in this book.)

Documentation on developer.apple.com is provided when you search on Core Data or coredata; in addition, videos from WWDC 2012 and WWDC 2013 are available to registered developers. Each year, the conference had several Core Data sessions. The overview in WWDC 2013 is Session 207, which is worth watching if you want further information on the topics covered in this chapter.

This chapter focuses on the WWDC 2013 changes and additions. The changes and additions help you manage iCloud and Core Data as they work together at runtime. The code in this chapter consists of targeted snippets to handle the various issues raised in the chapter. Listing 17.1 shows the class extension assumed in this chapter. There is more code depending on your app, but these properties are expected to exist for the code snippets.

Listing 17.1 Class Extension for This Chapter


@interface JFAppDelegate () {
id store; //1
}

@property (readonly, strong, nonatomic) NSURL *storeURL;
@property (readonly, strong, nonatomic)
NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic)
NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator
*persistentStoreCoordinator;

@end


1 The store instance variable can be any object because it is declared as id. This is a typical declaration for something that will be stored in a dictionary, as described in the following section.

Using the Options Dictionary

Many of the Core Data methods take an options NSDictionary as an argument. This is a very efficient way to pass in a number of arguments wrapped in a single message. For example, here is the completeaddPersistentStoreWithType:configuration:URL:options:error: declaration:

- (NSPersistentStore *)addPersistentStoreWithType: (NSString *)storeType
configuration: (NSString *)configuration
URL: (NSURL *)storeURL
options: (NSDictionary *)options
error: (NSError **)error

For a typical use, you would declare the local error variable as well as a dictionary to be passed into options:

NSError *error;
NSDictionary *userInfo =@{
NSAddedPersistentStoresKey: @[store],
NSRemovedPersistentStoresKey: @[store]
};

If you have a persistent store coordinator called psc, the full message in your code might be as follows:

[psc addPersistentStoreWithType: NSSQLLiteStoreType
configuration: nil
URL: storeURL // something you have set already
options: userInfo // the dictionary
error: &error];

As you will see, there are some new options you can put into your dictionary for various messages.

Fallback Stores

When you design for iCloud, you need to take into consideration the fact that iCloud may not always be available. In some cases, the absence of an Internet connection or an iCloud account is a show-stopper, but in many cases, you need some way to be able to continue without them.

How you do that depends on you and your app. For example, if your app is a multiplayer game, it’s hard to imagine that you can manage without another player. However, if the app is a game that requires some skills and strategy, perhaps a fallback approach is a single-user version that focuses on those skills and strategies.

For a great many apps, planning for the absence of the Internet or an iCloud account means storing data locally in one way or another and then synchronizing it with iCloud when it or Internet connectivity is restored.

At WWDC 2012, the synchronization for fallback stores was presented in detail. It is one of two areas that caused problems for developers over the year. (The other was data model migration, which is discussed later in this chapter.) The WWDC 2012 version of synchronization involved synchronizing Core Data transactions with iCloud. The transactions, rather than the entire database, could then be downloaded and reapplied to peer clients (that is, other devices under the same Apple ID iCloud account). Plenty of sample code was provided.

However, the process was reasonably complex, so the Core Data engineers came up with a simple solution: managing the fallback store moved from being the developer’s responsibility to being Core Data’s responsibility. Of course, this simple change is only conceptually simple—a lot of work from the Core Data team went into realizing the goal.

Core Data now logs console messages so that you can track which store is being used. The fallback store is used when the iCloud store is unavailable for reasons that you typically can’t control (the user signs out, the network connection drops, and so forth). The two console messages you may see indicate that local storage is not being used:

Core Data: Ubiquity peerID: <StoreName> - Using local storage:0

or that local storage is being used:

Core Data: Ubiquity peerID: <StoreName> - Using local storage:1

Whenever the store changes, you receive one message or the other on the console (viewed via Console app). Note that these are Console messages; for the corresponding notifications, read on.

Setting Up and Managing Persistent Stores

Core Data is now responsible for managing the fallback store, but you are responsible for setting up and managing a persistent store itself.

Setting Up a Persistent Store Asynchronously

With the fallback store maintained by Core Data, it can now be used by Core Data in new ways. Perhaps the most visible is a change to the way addPersistentStore functions. When you had to manage the fallback store, adding a persistent store required Core Data to go out to iCloud to find the store (if possible) and then to use it to set up the persistent store. Now that Core Data manages the fallback store, it can assume the fallback store is available. It uses it to add the persistent store: that method now returns quickly. After it returns, Core Data can go out to iCloud and do additional setup processing (usually retrieving updates that have been logged against the iCloud version of the store and that need to be applied against the fallback store locally). Thus, you no longer need to run addPersistentStore on a background thread.

Listing 17.2 shows a sample use of addPersistentStore on the main thread, which is now safe in WWDC 2013.

Listing 17.2 Adding a Persistent Store


NSError *error;

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:_storeURL
options:nil
error:&error]) {
// handle error
}


As part of the asynchronous setup, the processing involved in adding a persistent store is the same as in managing a change in the persistent store, as described in the following section.

Managing Persistent Store Changes

The sequence of events when a persistent store changes has been simplified. There are now five basic steps involved:

1. You receive NSPersistentStoreCoordinatorsStoresWillChange-Notification.

2. Save and reset your managed object context. If you use the names shown in Listing 17.1, your code would be similar to the following:
[_managedObjectContext save:];
[_managedObjectContext reset];

The argument after save is a pointer to an NSError.

3. Core Data removes the store.

4. You receive NSPersistentStoreCoordinatorsStoresDidChange-Notification.

5. Save the managed object context with the new store:

[_managedObjectContext save:];

If you are used to WWDC 2012, you may be thinking about dropping a persistent store and wiping out a managed object context. You don’t need to worry about those steps any more: these are the only steps you need to follow.

Listing 17.3 shows the code to register for the notifications and to handle them. Note that if the code is placed within a single block, you should declare the default notification center (dc) only once. In many cases, the code will be in several blocks, so you need declare it in each one.

Listing 17.3 Handling Persistent Store Changes


- (void)storesWillChange: (NSNotification *)n {
NSError *error;
if ([_managedObjectContext hasChanges]) {
[_managedObjectContext save:&error];
}

[_managedObjectContext reset];
// update UI
}

- (void)storesDidChange: (NSNotification *)n {
// update UI
}

NSNotificationCenter *dc =[NSNotificationCenter defaultCenter];
[dc addObserver:self
selector:@selector(storesWillChange:)
name:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:nil];

NSNotificationCenter *dc =[NSNotificationCenter defaultCenter];
[dc addObserver:self
selector:@selector(storesDidChange:)
name:NSPersistentStoreCoordinatorStoresDidChangeNotification
object:nil];


Managing Account Changes

Many users use a single iCloud account on all of their devices. The only time they change their iCloud account is when they get a new device—and, in many cases, that is handled automatically during setup.

Other people routinely use several iCloud accounts. One may be for home and the other for work. You, as a developer, are likely to have separate iCloud accounts for your personal use, your developer work, and perhaps yet another for your iBooks Author projects.

When you are developing an iCloud app, you have to be able to handle both scenarios. That basically means being able to handle account changes: the situation with a single iCloud account that doesn’t change is a dramatic simplification of the multiple account case.

Account changes in WWDC 2012 were handled with NSUbiquityIdentityTokenDidChangeNotification. After receiving that notification, you typically called two methods (again using the properties from Listing 17.1):

[_managedObjectContext reset];
[_persistentStoreCoordinator removePersistentStore: <old account store>];
[_persistentStoreCoordinator addPersistentStore: <new account store>];

Beginning with WWDC 2013, you respond to a different notification: NSPersistentStoreCoordinatorStoresWillChangeNotification. This requires the same process as described in the preceding section for managing persistent store changes. For persistent store changes and account changes, the bulk of the processing is now done asynchronously inside Core Data; you can stay on the main thread.

Database Migration

One of the features of Core Data is its ability to migrate from one database model version to another either automatically or with varying degrees of developer involvement. It appears from discussions on the Apple Developer Forums over the last year that some of the issues involved migration from one data model to another, slightly revised data model.

Putting Data Model Changes in Perspective

Database managements systems (DBMSs) have been in widespread use for decades. Originally a mainstay of mainframe computers, they migrated to personal computers with products such as Paradox, dBase, Access, and FileMaker. On many mainframe installations, there were designated database administrators (DBAs) who managed database schemas and often fine-tuned performance. Software engineers and programmers often created draft versions of schemas, which were then formally implemented by DBAs.

On personal computers, the DBA role often disappeared with the coming of the new breed of databases. Modifying a database schema or its user interface is something that can be done on the fly and often has been done by power users with products such as FileMaker.

Core Data provides wonderful tools to migrate your data model to a new version, but it’s worthwhile remembering that changing a data model or database schema is a significant operation. You might want to ratchet up your sensitivity to data model changes. Yes, it’s easy to implement them with Core Data, and, yes, when using iCloud, the data model changes can often be implemented without any loss of data. But remember how significant the changes are to your database.

With the WWDC 2013 version of Core Data, migration along with many Core Data features is much improved, but that doesn’t mean you should push the limits. Until you (and your users) are comfortable with migration, you may want to experiment with other options. Perhaps the most widely used alternative is to unload the data from the persistent store to a neutral format and then reload it to a new database. That is a more complicated way of approaching migration than using the automated tools, but it’s not a bad choice for your first few migrations.

Also note that, depending on your app, the ability to unload data from a Core Data persistent store to a neutral format may be a feature you want to include in your app. It is your user’s data, and he or she should be able to access it.

Starting Over

With Core Data now managing the fallback store, it became easier to wipe out a Core Data persistent store that uses iCloud. There are two primary reasons for doing this:

Image During development, you often fiddle with your data model and persistent store as you refine your app.

Image With some apps, there are moments when a user needs the ability to start over from scratch. A common example of this is a game where a user’s best scores and perhaps other information are stored until the slate is wiped clean. It’s easy to just reset the scores to zero, but if you’re storing a history of moves in the persistent store, it may be easier to start from scratch.

Judging from discussions and comments on Apple’s developer discussion boards, it seems that there were some occasions when iCloud synchronization could proceed even while a developer (or, ultimately, a user) was trying to wipe out a persistent store. Now that everything about the fallback store is managed by Core Data, there are tools to simply tell Core Data that you want to start over from scratch. You don’t have to do all the work yourself: Core Data wipes the persistent store out.

There are three basic ways to start over:

Image Rebuild the local store

Image Remove metadata and the local store file

Image Remove everything

Rebuilding the Local Store

For a message such as addPersistentStore, you can use the new NSPersistentStoreRebuildFromUbiquitousContentOption in your options dictionary. This will delete the local store and rebuild it from iCloud. If you use that option, addPersistentStore will always return an empty store because it switches over to the fallback store while the local store is being rebuilt from iCloud.

Removing Metadata and the Local Store File

Another new option, NSPersistentStoreRemoveUbiquitousMetadataOption, removes the local store file and metadata. By comparison with the previous option, it removes them but doesn’t rebuild them. If you are using migratePersistentStore: toURL:options:withType:error to create backups, you may want to use this option to remove the metadata from the backup.

Removing Everything

Finally, there is the method for wiping everything out. It is obviously very useful during development and testing. It may also be useful for certain apps even in production. Obviously, the risks of accidentally deleting everything are substantial, so if this option is included in a production version of an app, be certain to protect it from accidental use.

The code for this method is very robust and very fast. Its most critical part is accomplished with a single I/O command. However, you (and your users if you allow them to use it through an interface element) should take every possible precaution, including the following:

Image If the app is running on an iOS device, it should be tethered to a Mac for the fastest and surest possible connection.

Image Tethering also supplies power to the iOS device. Check the battery level (or have the user do so) before sending the message.

This is a synchronous message: nothing else can be processed until it finishes. Here is the class method you use:

+ (BOOL)removeUbiquitousContentAndPersistentStoreAtURL:(NSURL *)storeURLs
options:(NSDictionary *)options
error:(NSError**)error;

You need to pass in the name and URL keys in the dictionary, so a typical use with a persistent store coordinator called psc might be as follows:

NSError *error;
NSDictionary *options = @{
NSPersistentStoreUbiquitousContentNameKey: @"Store",
NSPersistentStoreUbiquitousContentURLKey: @"Subdir"
};
BOOL success = [psc removeUbiquitousContentAndPersistentStoreAtURL:storeURL
options:options
error:&error];

Chapter Summary

This chapter provided an overview of the WWDC 2013 updates to APIs and architectures for Core Data in iOS 7 and OS X Mavericks (10.9). Efficiency and stability improvements make Core Data and iCloud easier to use together. These improvements directly benefit both users and developers.

The two most significant changes are that Core Data now manages the fallback store itself and that a new class method is available for starting over with a blank slate.

Exercises

1. Create a basic Core Data–based app that uses iCloud and enter some data. Then, remove its stores using removeUbiquitousContentAndPersistentStoreAtURL:storeURL. You’ll need to use multiple devices to make certain that it is removed properly. This is something you will probably need to do during your testing. The steps and options in this chapter provide a number of ways to handle the removal. Try them all (and, of course, re-create the data store from scratch as needed). Don’t wait to learn the removal processes when you are in the middle of debugging a production version of an app.

2. As in the previous exercise, create a Core Data–based app that uses iCloud and enter some data. Turn off one device. (You could use Airplane Mode, but just powering it off is definitive.) Now, remove the stores as you did in Exercise 1. Power all devices back on. Does data automatically propagate from the device that was turned off during your store removal?