iCloud Programming Guide for Core Data

iCloud Programming
Guide for Core Data
Contents
About Using iCloud with Core Data 5
At a Glance 5
Use Core Data Atomic Stores for Small, Simple Storage 5
Use Core Data Transactional Stores for Large, Complex Storage 6
(iOS Only) Use Core Data Document Stores to Manage Documents in iCloud 6
Prerequisites 6
Using the SQLite Store with iCloud 7
Enabling iCloud Support 7
Adding an iCloud-Enabled Persistent Store to Core Data 8
Checkpoint 9
Reacting to iCloud Events 9
Core Data Configures Your Persistent Store 10
iCloud Performs a One-Time Setup 11
Core Data Posts Content Changes from iCloud 16
Core Data Helps You with Account Transitions 17
Seeding Initial Data 19
Detecting and Removing Duplicate Records 21
Checkpoint 23
Performing Schema Migrations 23
Checkpoint 24
Removing an iCloud-enabled Persistent Store 25
Rebuilding from iCloud 25
Disabling iCloud Persistence 25
Starting Over 25
Using Document Storage with iCloud 26
Creating and Opening Managed Documents 26
Configuring Managed Documents 26
Creating Managed Documents 27
Opening Managed Documents 27
Using a Managed Document’s Managed Object Context 28
Saving Managed Documents 29
Deleting Managed Documents 29
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
2
Contents
Best Practices 30
Optimizing Your App for iCloud 30
Store Only Essential Data in iCloud 30
Design Your Core Data Stack to Be Efficient 31
Know When to Save Your Context 31
Migrating User Data to iCloud 32
Updating Your App to Take Advantage of iOS 7 and OS X 10.9 32
Troubleshooting 33
Debugging Core Data Errors 33
Debugging Duplicate Data and Missing Data Issues 33
Debugging Performance Problems 34
Using the iCloud Debugging Tools 35
Understanding the iCloud Report 35
Interpreting Transfer Activity 35
Monitoring Document Status 38
Enabling iCloud Debug Logging 38
On iOS 38
On OS X 39
Document Revision History 40
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
3
Figures and Tables
Using the SQLite Store with iCloud 7
Figure 1-1
Figure 1-2
Figure 1-3
Figure 1-4
Figure 1-5
Figure 1-6
Figure 1-7
Enabling iCloud capability 8
Adding an iCloud-enabled persistent store 10
One-time setup 12
One-time setup with save 14
Content change import 16
Account transition 17
Account transition with save 18
Using the iCloud Debugging Tools 35
Figure 5-1
Figure 5-2
Figure 5-3
Figure 5-4
Figure 5-5
Table 5-1
Store initialization: Metadata uploaded 36
Store initialization: Transaction logs downloaded 36
Content changes: Transaction log uploaded 37
Content changes: High save frequency 37
Content changes: Transaction logs downloaded 38
iCloud document status descriptions 38
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
4
About Using iCloud with Core Data
iCloud is a cloud service that gives your users a consistent and seamless experience across all of their
iCloud-enabled devices. iCloud works with ubiquity containers—special folders that your app stores data in—to
manage your app’s cloud storage. When you add, delete, or make changes to a file in your app’s ubiquity
container, the system uploads the changes to iCloud. Other peers download the changes to keep your app up
to date.
To help you persist managed objects to the cloud, iCloud is integrated with Core Data. To use Core Data with
iCloud, you simply tell Core Data to create an iCloud-enabled persistent store. The iCloud service and Core
Data take care of the rest: The system manages the files in the ubiquity container that make up your persistent
store, and Core Data helps you keep your app up to date. To let you know when the content in your container
changes, Core Data posts notifications.
At a Glance
When you use Core Data, you have several storage models to choose from. Using Core Data with iCloud, you
have a subset of these options, as follows:
●
Atomic stores (for example, the binary store) load and save all of your managed objects in one go. Atomic
stores work best for smaller storage requirements.
●
Transactional stores (for example, the SQLite store) load and save only the managed objects that you’re
using and offer high–performance querying and merging. Transactional stores work best for larger, more
complex storage requirements.
●
Document storage (iOS only) works best for apps designed to use a document-based design paradigm.
Use document storage in combination with either an atomic or a transactional store.
When you decide on a storage model, consider the strengths of each store as well as the iCloud-specific
strengths discussed below.
Use Core Data Atomic Stores for Small, Simple Storage
iCloud supports XML (OS X only) and binary atomic persistent stores. Useful for small, simple storage
requirements, Core Data’s atomic-store support sacrifices merging and network efficiency for simplicity of use
for when your data rarely changes. When you use iCloud with an atomic persistent store, you work directly in
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
5
About Using iCloud with Core Data
Prerequisites
the ubiquity container. Binary (and XML) store files are themselves transferred to the iCloud servers; so whenever
a change is made to the data, the system uploads the entire store and pushes it to all connected devices. This
means that changes on one peer can overwrite changes made on the others.
iCloud treats Core Data atomic stores like any other file added to your app’s ubiquity container. You can learn
more about managing files in your app’s ubiquity container in iCloud Design Guide .
Use Core Data Transactional Stores for Large, Complex Storage
Core Data provides ubiquitous persistent storage for SQLite-backed stores. Core Data takes advantage of the
SQLite transactional persistence mechanism, saving and retrieving transaction logs—logs of changes—in your
app’s ubiquity container. The Core Data framework’s reliability and performance extend to iCloud, resulting
in dependable, fault-tolerant storage across multiple peers. Continue reading this document to learn more
about how to use iCloud with an SQLite store.
(iOS Only) Use Core Data Document Stores to Manage Documents in iCloud
The UIManagedDocument class is the primary mechanism through which Core Data stores managed documents
in iCloud on iOS. The UIManagedDocument class manages the entire Core Data stack for each document in
a document-based app. Changes to managed documents are automatically persisted to iCloud. By default,
managed documents are backed by SQLite-type persistent stores, but you can choose to use atomic stores
instead. While the steps you take to integrate the UIManagedDocument class into your app differ, the
model-specific guidelines and best practices you follow are generally the same. You can find additional
implementation strategies and tips in “Using Document Storage with iCloud” (page 26).
Prerequisites
iCloud is a service that stores your app’s data in the cloud and makes it available to your users’ iCloud-enabled
devices. Before using Core Data’s iCloud integration, you should read more about iCloud in iCloud Design
Guide . In addition, this guide assumes a working knowledge of Core Data, a powerful object graph and data
persistence framework. For more information about the Core Data framework, see “Introduction to Core Data
Programming Guide” in Core Data Programming Guide .
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
6
Using the SQLite Store with iCloud
Core Data’s iCloud integration combines SQLite-type persistent store access with iCloud storage. Follow the
implementation strategy in this chapter to create a robust, high-performance iCloud-enabled app. Using these
guidelines and sample code, you will learn how to:
●
Enable iCloud support
●
Persist managed objects to iCloud
●
Understand content change and availability events
●
Seed initial data
●
Remove duplicate records
Enabling iCloud Support
To begin using iCloud in your app, you must first enable iCloud support for your app in the cloud and in your
project. This effectively links the two together. After enabling iCloud support you can begin using your app’s
ubiquity container, an app sandbox in the cloud. You perform two tasks to add iCloud to a new Core Data app:
1.
Create an App ID on the Developer Portal.
Create an app ID on the iOS Dev Center at developer.apple.com. Apps that use iCloud cannot use wildcard
identifiers. Set your project to code-sign using the App ID you just created.
2.
Enable iCloud in Xcode.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
7
Using the SQLite Store with iCloud
Adding an iCloud-Enabled Persistent Store to Core Data
Navigate to your app’s target in the project settings in Xcode, and select the capabilities pane. Click the
switch next to iCloud, as shown in Figure 1-1. Xcode configures your App ID in the developer portal and
adds entitlements to your app. These entitlements give your app permission to access its ubiquity
container(s). By default, Xcode creates one ubiquity container identifier using your App ID.
Figure 1-1
Enabling iCloud capability
Note: iCloud does not support ordered relationships.
Adding an iCloud-Enabled Persistent Store to Core Data
After you enable iCloud, your app can begin persisting documents and data to your ubiquity container. When
you create a SQLite-type Core Data persistent store with iCloud support enabled, Core Data uses the ubiquity
container to persist model objects to iCloud. Therefore, you must create an iCloud-enabled persistent store to
start using iCloud. See “Migrating User Data to iCloud” (page 32) for additional tasks that must be performed
when enabling iCloud in an existing Core Data app.
As you would for a typical Core Data app, add an NSSQLiteStoreType type store to your persistent store
coordinator. You pass the following key-value pair in the options dictionary to enable iCloud.
●
NSPersistentStoreUbiquitousContentNameKey
An NSString object with a name used to identify the store:
NSURL *documentsDirectory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory
URLByAppendingPathComponent:@"CoreData.sqlite"];
NSError *error = nil;
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
8
Using the SQLite Store with iCloud
Reacting to iCloud Events
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:<# your managed object model #>];
NSDictionary *storeOptions =
@{NSPersistentStoreUbiquitousContentNameKey: @"MyAppCloudStore"};
NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:storeOptions
error:&error];
NSURL *finaliCloudURL = [store URL];
Note: The store’s final URL may be different from the URL you passed in. If you need a reference to
the persistent store, use the URL generated by the coordinator rather than the one you created.
For a description of additional options that may be specified, see NSPersistentStoreCoordinator Class Reference .
Checkpoint
At this point, your app can create an iCloud-enabled Core Data persistent store. You’ve enabled iCloud in
Xcode, created a ubiquitous content name, and added an iCloud-enabled persistent store with that name to
your persistent store coordinator.
Setup: Create a Core Data stack and add a persistent store with the ubiquitous content name option.
Test: Run the app on a device. If Core Data successfully created and configured an iCloud-enabled persistent
store, the framework logs a message containing “Using local storage: 1,” and, later, another message containing
“Using local storage: 0”.
Reacting to iCloud Events
iCloud notifies the Core Data framework when changes occur to the data inside your app’s ubiquity container
or to the availability of the container itself. After receiving messages from iCloud, Core Data translates these
generic state changes into events that make sense in the context of your Core Data stack and that correspond
to actionable steps you take to keep your app’s user interface up to date. Core Data tells your app about these
events using notifications.
Before you implement your responses to iCloud events, it’s useful to keep some general guidelines in mind.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
9
Using the SQLite Store with iCloud
Reacting to iCloud Events
●
Pass the relevant persistent store coordinator as the object to filter the notifications your handlers receive.
Otherwise, your app could receive notifications from other persistent store coordinators.
●
Deregister for notifications when your view controller is no longer onscreen.
●
Dispatch onto the queue on which your context lives or use the performBlock: API. Notifications may
not be posted on the same thread as your managed object context.
●
Dispatch onto the main thread when you update your user interface.
It is especially important to test an iCloud-enabled iOS app on an actual device. iOS Simulator doesn’t accurately
simulate the use of iCloud under real-world conditions. For more information about testing your app and fixing
problems, see “Troubleshooting” (page 33).
Core Data Configures Your Persistent Store
The first iCloud event happens after you add an iCloud-enabled persistent store to your persistent store
coordinator. Core Data returns your persistent store from the method invocation and immediately posts an
NSPersistentStoreCoordinatorStoresDidChangeNotification notification to let your app know
that the persistent store has been configured for use. This notification’s user info dictionary contains the created
NSPersistentStore object. In Figure 1-2, your app adds an iCloud-enabled persistent store, which causes
Core Data to post a notification and begin container setup. In the handler for the notification, the persistent
store is also available in the notification’s user info dictionary.
Figure 1-2
Adding an iCloud-enabled persistent store
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
10
Using the SQLite Store with iCloud
Reacting to iCloud Events
Tip: Because this notification is posted immediately after you invoke
addPersistentStoreWithType:configuration:URL:options:error:, you must subscribe to
this notification beforehand if you want to consistently enable your user interface in response to an
NSPersistentStoreCoordinatorStoresDidChangeNotification notification.
Checkpoint
At this checkpoint you have configured Core Data to tell your app when an iCloud-enabled persistent store is
ready for use. After adding a new iCloud-enabled persistent store in the previous section, you created a
notification handler to respond to NSPersistentStoreCoordinatorStoresDidChangeNotification
notifications.
Setup: Add an NSLog statement inside of your stores-did-change notification handler.
Test: Completely remove your app from your device and then click the run button in Xcode to reinstall your
app and reopen it. If Core Data successfully created and configured an iCloud-enabled persistent store, the
framework invokes your notification handler and your NSLog statement will print to the console in Xcode.
iCloud Performs a One-Time Setup
When a user launches your iCloud-enabled app for the first time, Core Data and the system work together to
create your app’s ubiquity container. Ubiquity container initialization is a long-running operation that relies
on network connectivity with the iCloud service. Because of this, the Core Data framework provides you with
a temporary local persistent store that you use while the work is performed in the background. On subsequent
app launches, the ubiquity container is already initialized and Core Data is associated with the iCloud-enabled
persistent store.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
11
Using the SQLite Store with iCloud
Reacting to iCloud Events
Reacting to One-Time Setup
After the one-time setup is complete, Core Data migrates records from the temporary local persistent store to
the iCloud-enabled store. When Core Data finishes moving all of your data, the temporary local store and the
contents of your managed object context(s) are invalid. To prepare you for this event, Core Data posts two
notifications after it finishes moving your data: one just before it swaps out the stores and invalidates your
data, and another right after.
Figure 1-3
One-time setup
As illustrated in Figure 1-3, when setup finishes, Core Data posts an
NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification
handler, you reset your managed object context and drop any references to existing managed objects. As soon
as your handler finishes executing, these objects are no longer valid. You must also prevent any interaction
with the temporary local store while Core Data transitions to the iCloud-enabled store. Disabling your user
interface is the simplest way to do this in a UI-driven app and is in most cases invisible to the user. This is
because the time between the will-change notification and the did-change notification is extremely short.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:self.managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self.managedObjectContext performBlock:^{
[self.managedObjectContext reset];
}];
// drop any managed object references
// disable user interface with setEnabled: or an overlay
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
12
Using the SQLite Store with iCloud
Reacting to iCloud Events
}];
Core Data finishes swapping out the temporary local store with the iCloud-enabled store and posts an
NSPersistentStoreCoordinatorStoresDidChangeNotification notification. The notification’s user
info dictionary contains the iCloud-enabled store as the object associated with the
NSAddedPersistentStoresKey key. In this notification’s handler, you reenable your user interface and
refetch.
Note: NSPersistentStoreCoordinatorStoresWillChangeNotification and
NSPersistentStoreCoordinatorStoresDidChangeNotification notifications may be nested.
Checkpoint
At this point, your notification handlers disable, enable, and refresh your user interface when one-time setup
is finished. You’ve registered for NSPersistentStoreCoordinatorStoresWillChangeNotification
notifications, where you disable your user interface. You’ve also registered for
NSPersistentStoreCoordinatorStoresDidChangeNotification notifications, where you reenable
your app’s user interface and refetch your data.
Setup: On your iOS device, begin with airplane mode enabled. If you’re creating a Mac app, disable every
network interface instead. Completely remove your app from your device as well.
Test: Run your app and create a few records. Disable airplane mode or reenable a network interface and wait
for Core Data to print “Using local storage: 0” to the console in Xcode. Core Data invokes your notification
handlers and your records disappear. In the next section you’ll learn how to persist in-memory changes.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
13
Using the SQLite Store with iCloud
Reacting to iCloud Events
Persisting In-Memory Changes
If your managed object context has changes that have not been committed to the temporary store, save those
changes so that Core Data will migrate them to the iCloud-enabled persistent store. Your notification handler
behaves slightly differently: Rather than immediately resetting your managed object context, you check for
changes in your managed object context and invoke save:. Otherwise, you follow the same process as
described in the previous section, resetting your context and disabling your user interface.
Figure 1-4
One-time setup with save
After saving your managed object context, Core Data posts another
NSPersistentStoreCoordinatorStoresWillChangeNotification notification. Core Data continues
to post this notification until you no longer invoke save: on your managed object context and instead reset
it. This loop is illustrated above in Figure 1-4 and implemented below.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:self.managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
// disable user interface with setEnabled: or an overlay
[self.managedObjectContext performBlock:^{
if ([self.managedObjectContext hasChanges]) {
NSError *saveError;
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
14
Using the SQLite Store with iCloud
Reacting to iCloud Events
if (![self.managedObjectContext save:&saveError]) {
NSLog(@"Save error: %@", saveError);
}
} else {
// drop any managed object references
[self.managedObjectContext reset];
}
}];
}];
Checkpoint
When one-time setup is finished, your app’s notification handlers disable, enable, and refresh your user interface.
You’ve registered for the NSPersistentStoreCoordinatorStoresWillChangeNotification and
NSPersistentStoreCoordinatorStoresDidChangeNotification notifications to handle the transition
between the temporary local store and the iCloud-enabled persistent store after first-time ubiquity container
setup finishes.
Setup: Begin with airplane mode enabled on your iOS device, or all network interfaces disabled on your Mac.
Completely remove your app from your device as well.
Test: Run your app and create a few records. Disable airplane mode or reenable a network interface. Then wait
for Core Data to print “Using local storage: 0” to the console in Xcode. Core Data invokes your notification
handlers, and your records persist after your notification handler executes a new fetch request.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
15
Using the SQLite Store with iCloud
Reacting to iCloud Events
Core Data Posts Content Changes from iCloud
Core Data imports changes persisted to iCloud from other peers after first-time setup and while your app is
running. Core Data represents this event as shown in Figure 1-5.
Figure 1-5
Content change import
When the ubiquity container receives changes from iCloud, Core Data posts an
NSPersistentStoreDidImportUbiquitousContentChangesNotification notification. This notification’s
userInfo dictionary is structured similarly to that of an NSManagedObjectContextDidSaveNotification
notification except for that it contains NSManagedObjectID instances rather than NSManagedObject instances.
Therefore you can merge in changes from other peers in the same way that you merge changes from other
managed object contexts. Call mergeChangesFromContextDidSaveNotification: on your managed
object context, passing in the notification object posted by Core Data.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:self.managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self.managedObjectContext performBlock:^{
[self.managedObjectContext
mergeChangesFromContextDidSaveNotification:note];
}];
}];
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
16
Using the SQLite Store with iCloud
Reacting to iCloud Events
Core Data will also post one or more of these notifications after creating or rebuilding a persistent store and
when the framework imports existing records from iCloud. See “Core Data Helps You with Account
Transitions” (page 17) for more information.
Checkpoint
At this checkpoint, your app’s change notification handler merges changes from other peers into your managed
object context. You’ve registered for the
NSPersistentStoreDidImportUbiquitousContentChangesNotification notification, where you
decide which data refreshing strategy to use—in-memory merging or data refetching.
Setup: Install your app on two devices.
Test: Run your app on both devices, and create a few distinct records on each. Wait for Core Data to print
“Using local storage: 0” to the console in Xcode. Content change notifications typically follow soon after, and
each device adds the other’s records.
Core Data Helps You with Account Transitions
Account transitions can impact all of your app’s data. A transition occurs for all of the following four events:
the initial import from iCloud has completed, the iCloud account associated with the device changes, iCloud
is disabled, or the user deletes your app’s data.
Reacting to Account Transitions
When the system informs Core Data of an account transition while your app is running, Core Data works with
your app to prepare for the event. Figure 1-6 shows the timeline of an account transition event.
Figure 1-6
Account transition
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
17
Using the SQLite Store with iCloud
Reacting to iCloud Events
Just as during one-time setup, Core Data posts an
NSPersistentStoreCoordinatorStoresWillChangeNotification notification, informing you that an
account transition is in progress. In addition to the added and removed persistent stores, the notification’s
userInfo dictionary also contains a transition type. The transition type, associated with the
NSPersistentStoreUbiquitousTransitionTypeKey key, is an NSNumber object mapped to a key in the
NSPersistentStoreUbiquitousTransitionType enum. You can use this information to find out why a
transition is happening.
In your notification handler, you reset your managed object context and drop any references to existing
managed objects. As soon as your handler finishes executing, these objects are no longer valid. You must also
prevent any interaction with the iCloud-enabled store while Core Data finishes transitioning. In a UI-driven
app, disabling your user interface is the simplest way to do this. When the current iCloud account is removed,
Core Data saves your store to an account-specific archive. If the user signs into the same account again in the
future, Core Data unarchives the data and resumes uploading.
Important: Because iOS periodically removes stores associated with accounts that are no longer active,
there is no guarantee that records saved after you receive the
NSPersistentStoreCoordinatorStoresWillChangeNotification notification will be persisted to
iCloud.
Persisting In-Memory Changes
As it behaved in first-time setup, Core Data gives your app a chance to save data that wasn’t persisted by the
time the notification was posted. Again, because iOS deletes old stores associated with iCloud accounts that
are no longer active on the device, your changes may never be saved or sent to the cloud.
Figure 1-7
Account transition with save
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
18
Using the SQLite Store with iCloud
Seeding Initial Data
In contrast with how it behaved in one-time setup, Core Data gives your app only one chance to save; it won’t
post another NSPersistentStoreCoordinatorStoresWillChangeNotification notification. As
illustrated in Figure 1-7 above, saving your managed object context does not cause a loop.
Checkpoint
At this point, your app’s two store change notification handlers gracefully transition your app between persistent
stores as an iCloud account is added or removed. You’ve modified your existing notification handlers to check
the NSPersistentStoreUbiquitousTransitionType and respond accordingly.
Setup: Install your app on a device with iCloud enabled.
Test: Run your app on both devices, and create a few distinct records. Wait for Core Data to print “Using local
storage: 0” to the console in Xcode. Then change iCloud accounts—your app’s data should disappear, and
your persistent store should be rebuilt with the new iCloud account’s data.
Seeding Initial Data
If your app is packaged with a prebuilt database or if a previous version of your app did not persist to iCloud,
your persistent store coordinator can create an iCloud-enabled persistent store and migrate the records in a
single step. To learn when to store seeded data in iCloud, see “Store Only Essential Data in iCloud” (page 30).
After creating your persistent store coordinator and adding your existing persistent store, follow the same
steps in “Adding an iCloud-Enabled Persistent Store to Core Data” (page 8), replacing the call to
addPersistentStoreWithType:configuration:URL:options:error: with a call to
migratePersistentStore:toURL:options:withType:error:. Pass in your existing persistent store as
the first argument in the method. Migrating a persistent store is a synchronous task, unlike adding an
iCloud-enabled persistent store. You dispatch migration onto a background queue and then update your user
interface after migration is complete.
Finally, mark seeding as complete using iCloud key-value storage so that other peers do not seed the same
data. See “Designing for Key-Value Data in iCloud” in iCloud Design Guide for more about using iCloud key-value
storage.
NSURL *documentsDirectory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory
URLByAppendingPathComponent:@"CoreData.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:<# your managed object model #>];
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
19
Using the SQLite Store with iCloud
Seeding Initial Data
NSUbiquitousKeyValueStore *kvStore = [NSUbiquitousKeyValueStore defaultStore];
if (![kvStore boolForKey:@"SEEDED_DATA"]) {
NSURL *seedStoreURL = <# path to your seed store #>
NSError *seedStoreError;
NSDictionary *seedStoreOptions = @{NSReadOnlyPersistentStoreOption: @YES};
NSPersistentStore *seedStore =
[coord addPersistentStoreWithType:<#seed store type#>
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions =
@{NSPersistentStoreUbiquitousContentNameKey: @"MyAppCloudStore"};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSPersistentStore *iCloudStore =
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&error];
NSURL *finaliCloudURL = [iCloudStore URL];
[kvStore setBool:YES forKey:@"SEEDED_DATA"];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// Update your user interface
}];
});
} else {
// Add persistent store normally
}
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
20
Using the SQLite Store with iCloud
Detecting and Removing Duplicate Records
Note: Run deduplication (see “Detecting and Removing Duplicate Records”) after the initial iCloud
import process completes in case other peers also seeded data.
Detecting and Removing Duplicate Records
When a user interacts with your app on multiple devices, your app can end up having two or more of the same
managed objects. To solve this problem, the steps that follow explain how to detect and remove duplicate
objects. Deduplication is the process of finding and deleting these duplicates. There are several common
situations in which your app should perform deduplication:
●
Your app seeded initial data.
●
Your users frequently create the same records on multiple peers.
●
Your app was updated from a version without iCloud support, and you've migrated existing data.
For example, a user stores recipes in a recipe app on two peers. Because the first version of the app did
not support iCloud, the user created the same recipes on both devices. The user upgrades the app on
both peers to version two, which now supports iCloud integration. After migrating the user’s recipes to
iCloud, both devices have duplicate recipes.
In each of these situations, the tasks you complete are the same:
1.
Choose a property or a hash of multiple properties to use as a unique ID for each record.
NSString *uniquePropertyKey = <# property to use as a unique ID #>
NSExpression *countExpression = [NSExpression expressionWithFormat:@"count:(%@)",
uniquePropertyKey];
NSExpressionDescription *countExpressionDescription = [[NSExpressionDescription
alloc] init];
[countExpressionDescription setName:@"count"];
[countExpressionDescription setExpression:countExpression];
[countExpressionDescription setExpressionResultType:NSInteger64AttributeType];
NSManagedObjectContext *context = <# your managed object context #>
NSEntityDescription *entity = [NSEntityDescription entityForName:@"<# your entity
#>" inManagedObjectContext:context];
NSAttributeDescription *uniqueAttribute = [[entity attributesByName]
objectForKey:uniquePropertyKey];
2.
Fetch the number of times each unique value appears in the store.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
21
Using the SQLite Store with iCloud
Detecting and Removing Duplicate Records
The context returns an array of dictionaries, each containing a unique value and the number of times that
value appeared in the store.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"<#
your entity #>"];
[fetchRequest setPropertiesToFetch:@[uniqueAttribute, countExpression]];
[fetchRequest setPropertiesToGroupBy:@[uniqueAttribute]];
[fetchRequest setResultType:NSDictionaryResultType];
NSArray *fetchedDictionaries = <# execute a fetch request against your store #>
3.
Filter out unique values that have no duplicates.
NSMutableArray *valuesWithDupes = [NSMutableArray array];
for (NSDictionary *dict in fetchedDictionaries) {
NSNumber *count = dict[@"count"];
if ([count integerValue] > 1) {
[valuesWithDupes addObject:dict[@"<# property used as the unique ID #>"]];
}
}
4.
Use a predicate to fetch all of the records with duplicates.
Use a sort descriptor to properly order the results for the winner algorithm in the next step.
NSFetchRequest *dupeFetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"<#
your entity #>"];
[dupeFetchRequest setIncludesPendingChanges:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"<# property used as
the unique ID #> IN (%@)", valuesWithDupes];
[dupeFetchRequest setPredicate:predicate];
5.
Choose the winner.
After retrieving all of the duplicates, your app decides which ones to keep. This decision must be
deterministic, meaning that every peer should always choose the same winner. Among other methods,
your app could store a created or last-changed timestamp for each record and then decide based on that.
MyClass *prevObject;
for (MyClass *duplicate in dupes) {
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
22
Using the SQLite Store with iCloud
Performing Schema Migrations
if (prevObject) {
if ([duplicate.uniqueProperty isEqualToString:prevObject.uniqueProperty])
{
if ([duplicate.createdTimestamp compare:prevObject.createdTimestamp]
== NSOrderedAscending) {
[context deleteObject:duplicate];
} else {
[context deleteObject:prevObject];
prevObject = duplicate;
}
} else {
prevObject = duplicate;
}
} else {
prevObject = duplicate;
}
}
Remember to set a batch size on the fetch and whenever you reach the end of a batch, save the context.
Checkpoint
At this point, your app can deterministically remove duplicate records.
Setup: Open the previous non-iCloud-enabled version of your app on two devices.
Test: Create several of the same records on both devices. Update to the most recent version of the app on
both devices. After both devices have migrated their stores to iCloud (see “Migrating User Data to iCloud” (page
32) for more about migration), deduplication has been run, and both devices have communicated these
changes to iCloud, check to make sure that there are no duplicate or missing records.
Performing Schema Migrations
During development of your app and between versions, you may change the Core Data model that defines
your entities. You can use a process called schema migration to migrate your app’s existing data to use the
new model. When you use the SQLite store with iCloud, the store supports only lightweight migration. (For
more about lightweight migration, see “Lightweight Migration” in Core Data Model Versioning and Data
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
23
Using the SQLite Store with iCloud
Performing Schema Migrations
Migration Programming Guide .) Because iCloud-enabled Core Data apps cannot persist changes between
different versions of your app that use use different schemas, your app will catch up and previously too-new
versions will merge in the older version’s changes only after it has been updated to the same version.
NSURL *documentsDirectory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory
URLByAppendingPathComponent:@"CoreData.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:<# your managed object model #>];
NSDictionary *storeOptions = @{NSPersistentStoreUbiquitousContentNameKey:
@"MyAppCloudStore",
NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption: @YES};
NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:storeOptions
error:&error];
NSURL *finaliCloudURL = [store URL];
Checkpoint
At this point, your app can smoothly perform schema migrations with an iCloud-enabled persistent store.
Setup: Create a second version of your app with an updated schema that’s compatible with lightweight
migration.
Test: Install the first version of the app on a device. Perform actions that create records, and set attributes that
are different in the second version of your schema. Use a second device running the same app version to verify
that the records have been persisted to iCloud. Then install and run the updated version of the app on your
device. Verify that the records have migrated correctly.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
24
Using the SQLite Store with iCloud
Removing an iCloud-enabled Persistent Store
Removing an iCloud-enabled Persistent Store
While developing your app (and occasionally in real-world usage), you might need to temporarily or permanently
remove your iCloud-enabled persistent store. There are three kinds of store removal: The first, rebuilding from
iCloud, deletes the local store and recreates it from data in iCloud. The second, disabling iCloud persistence,
tells Core Data to stop persisting the local data to iCloud and does not delete any data. Finally, starting over
wipes your app’s data from your devices and from iCloud, and is by far the most destructive.
Rebuilding from iCloud
To remove local data and start fresh with the existing records in iCloud, pass the
NSPersistentStoreRebuildFromUbiquitousContentOption option with a value of @YES when you
add your iCloud-enabled persistent store to the persistent store coordinator. Include any iCloud options you
usually include.
Note: Because addPersistentStoreWithType:configuration:URL:options:error: returns
immediately and imports records asynchronously, the persistent store returned from this method
is empty. Later on, you receive one or more import notifications that you merge to bring your store
up to date with iCloud.
Disabling iCloud Persistence
To stop your store from persisting to iCloud, you don’t simply omit the
NSPersistentStoreUbiquitousContentNameKey option when you add your persistent store. To completely
remove ubiquitous metadata and disable iCloud persistence, instead migrate your store to a new location and
pass the NSPersistentStoreRemoveUbiquitousMetadataOption option with a value of @YES in your
call to migratePersistentStore:toURL:options:withType:error:.
Starting Over
Warning: Performing the following task permanently and irrevocably deletes all of your app’s records
stored on every device and the records that Core Data has persisted to iCloud. None of your app’s data
can be recovered. Use this method only during development.
To remove your store permanently from iCloud and every peer (including the current device), invoke the
removeUbiquitousContentAndPersistentStoreAtURL:options:error: class method on the
NSPersistentStoreCoordinator class. This method is synchronous, relies on network reachability, and is
potentially long-running. You must not use your persistent store until the method returns. At that point, Core
Data transitions to an empty store.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
25
Using Document Storage with iCloud
Core Data document-based iOS apps combine the power and flexibility of Core Data with a document-centric
paradigm. You use UIManagedDocument objects that each manage an entire Core Data stack. Follow the
implementation strategy in this chapter to persist managed documents to the cloud. Using these guidelines,
you will learn how to:
●
Create and open managed documents
●
Use managed documents
●
Save managed documents to iCloud
●
Delete managed documents
By default, the UIManagedDocument class uses SQLite transactional persistent storage. Therefore after you
create a managed document, see “Reacting to iCloud Events” (page 9) to learn how to listen and respond to
iCloud events.
Note: In OS X, the NSPersistentDocument class does not support iCloud.
Creating and Opening Managed Documents
A managed document is represented on disk as a file package, and it includes its own Core Data store within
the package. You save managed documents to a ubiquity container to persist them to iCloud. Before using
your app’s ubiquity container, see “Enabling iCloud Support” (page 7) to learn how to configure iCloud for
your app.
Configuring Managed Documents
To create or open a managed document, you initialize a UIManagedDocument object with a location outside
of your app’s ubiquity container.
If you are creating a new managed document, choose a location to save your document inside the app’s
Documents subdirectory. Otherwise, use the existing managed document’s location. Finally, use the
NSFileManager class to decide whether you should create a new document or open the existing document.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
26
Using Document Storage with iCloud
Creating and Opening Managed Documents
NSURL *documentURL = <#path in the Documents directory#>
UIManagedDocument *document = [[UIManagedDocument alloc]
initWithFileURL:documentURL];
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption: @YES};
document.persistentStoreOptions = options;
if ([[NSFileManager defaultManager] fileExistsAtPath:[documentURL path]]) {
// Open existing document
} else {
// Create new document
}
Creating Managed Documents
After you’ve configured your managed document object, invoke the
saveToURL:forSaveOperation:completionHandler: method on the document and pass the
UIDocumentSaveForCreating operation option. This method creates the managed document’s file package
inside your app’s ubiquity container.
...
if ([[NSFileManager defaultManager] fileExistsAtPath:[documentURL path]]) {
...
} else {
[document saveToURL:documentURL forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
// Begin using managed document.
} else {
// Handle the error.
}
}];
}
Opening Managed Documents
After you’ve configured your managed document object, call openWithCompletionHandler: to open it.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
27
Using Document Storage with iCloud
Using a Managed Document’s Managed Object Context
...
if ([[NSFileManager defaultManager] fileExistsAtPath:[documentURL path]]) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
// Begin using the managed document.
} else {
// Handle the error.
}
}];
} else {
...
}
Using a Managed Document’s Managed Object Context
To support asynchronous data writing, Core Data uses a pair of nested managed object contexts: The parent
context is created on a private thread, and the child context is created on the main thread. You get the child
context from the managedObjectContext property. Because you should typically work with the child context,
perform all operations using that context on the main thread.
If appropriate, you can load data from a background thread directly to the parent context.
You can get the parent context using parentContext. When you load data to the parent context, you avoid
interrupting the child context’s operations. You can retrieve data loaded in the background simply by executing
a fetch.
In addition to receiving the notifications typically posted by Core Data, apps that use iCloud receive additional,
iCloud-specific state change notifications. You can register to receive these notifications when you need
additional insight into the the iCloud persistence process. If your managed document is backed by a SQLite-type
persistent store (the default), read “Reacting to iCloud Events” (page 9) to learn about responding to SQLite
persistent store events. If instead you have configured your managed document to use an atomic store, see
“Use Core Data Atomic Stores for Small, Simple Storage” (page 5).
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
28
Using Document Storage with iCloud
Saving Managed Documents
Note: For iCloud, you cannot use the additional-content APIs (described in “Customizing Read and
Write Operations” in UIManagedDocument Class Reference ).
Saving Managed Documents
To save a managed document in iOS, you can use any of the following approaches, with the most recommended
approach listed first:
●
Use the inherited Auto Save behavior provided by the UIDocument superclass. For most apps, this approach
provides good performance and low network traffic.
●
If your app design requires explicit control over when pending changes are committed, use the UIDocument
method saveToURL:forSaveOperation:completionHandler:.
If you perform an explicit save operation in an iCloud-enabled app, be aware that you are generating
additional network traffic—multiplied by the number of devices connected to the iCloud account.
If you have a specific case in which neither of the two preceding approaches works, such as importing a large
quantity of data in the background, you can explicitly save the document’s internal context. When you do,
keep in mind that a managed document has two contexts. The one it presents to you is actually a child of a
second context that the document uses internally to communicate with the document’s Core Data store.
If you save only the document’s public context, you’re not committing changes to the store; you still end up
relying on Auto Save. To explicitly save a document’s internal context, explicitly save both contexts. For more
information, read “Saving Changes” in Core Data Programming Guide .
If you save the internal context directly, you sidestep other important operations that the document performs.
Deleting Managed Documents
To delete a managed document, first close the document. Then delete the following two directories, using
coordinated write operations:
●
The directory for the Core Data change logs for the managed document
●
The managed document’s file package
To obtain the location of the directory containing the change log files, view the document’s
DocumentMetadata.plist property list file. Retrieve the name and URL that you set when creating the
document.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
29
Best Practices
Follow the tips and guidelines discussed in this chapter to improve your app’s user experience, stability, and
performance. In general, use an iCloud-enabled persistent store as you would a local persistent store, while
keeping several key design differences in mind.
Optimizing Your App for iCloud
Traditionally, you profile and optimize for memory, storage, and CPU usage. With iCloud, there are other
important factors—namely network availability, bandwidth, and multiple peers. Core Data’s iCloud integration
handles these issues for you, but by following these best practices, you can directly impact the quality of your
app and the degree to which Core Data can optimize for you.
Store Only Essential Data in iCloud
Bandwidth, network availability, and storage are limited resources and have real, financial consequences for
your users. When you design your schema, store in iCloud only information that can’t be re-created.
Core Data’s managed object model configuration feature simplifies working with data that’s split between two
storage locations. With model configurations, you can set where specific entities—or even individual
attributes—are stored. Use managed object model configuration to separate attributes and entities that need
only be stored locally from those that should be persisted to iCloud. See “Managed Object Models” in Core
Data Programming Guide for further discussion about model configurations. Additionally, Core Data efficiently
persists external data to iCloud. Use the external data setting on attributes that are likely to hold more than 4
KB of data.
For an example of how to reduce what you store in iCloud, consider a Newsstand app that downloads and
caches high-resolution magazines in Core Data and has a feature that allows the user to add bookmarks. Rather
than store the entire model in iCloud, the app stores only the bookmark entities, with attributes containing
the unique identifier of the magazine that each bookmark corresponds to.
A good approach is that if your app seeds a large set of initial data, it should not be stored in iCloud, because
those records can be re-created. If the records are to be modified, it may be beneficial to design a schema and
associated configurations that allow you to store only the changes in iCloud.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
30
Best Practices
Optimizing Your App for iCloud
Design Your Core Data Stack to Be Efficient
iCloud-enabled Core Data apps are not only aware of data created on the device, they’re aware of data on
other devices. It’s a good idea to keep this fact in mind when designing your Core Data stack.
For example, you traditionally use merge policies when you want to merge changes that were made in separate
managed object contexts and persistent store coordinators. With iCloud, in contrast, you use merge policies
to merge changes made to a record on multiple peers. For further discussion of merge policies, see “Change
Management” in Core Data Programming Guide .
Avoid using nested managed object contexts with iCloud-enabled persistent stores. Because changes saved
in child contexts are not persisted to iCloud until the parent context is also saved, nested contexts create
unnecessary complexity. If your app doesn’t persist changes to iCloud quickly, you may confuse your app’s
users or, worse, fail to meet their expectations.
Know When to Save Your Context
When you save a managed object context, Core Data creates, in the ubiquity container, a set of transaction
changes that are guaranteed to be applied together on other peers. The size of this transaction set, and the
frequency with which you save your context, both directly impact the performance of your app.
You decide when to save your managed object context by mapping actions in your user interface to the
managed objects that are created, changed, and deleted in your model. Because a save corresponds to a
complete set of changes that will be applied at the same time, you meet users’ expectations when you consider
the events the user expects to be persisted to other peers. By tuning your save points to match complete sets
of changes, you meet users’ expectations, avoid incomplete data from being persisted to other peers, and
reduce the complexity of merging. You also increase performance by limiting the number of changes in a
single merge and by limiting the frequency at which your context performs a merge.
For example, consider a recipe app that persists the user’s recipes to iCloud. Rather than save the context every
time an attribute is set, the context is saved when the user finishes creating an entire recipe. Because factors
like network availability, bandwidth, and storage were taken into account, each peer will either receive the
entire recipe or nothing, rather than a partial record.
If your app creates, changes, or deletes managed objects in large batches, be sure to save and reset your
managed object context frequently or divide the task up into smaller parts so that you divide the changes
among multiple transaction containers.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
31
Best Practices
Migrating User Data to iCloud
Migrating User Data to iCloud
How you migrate existing user data into the iCloud container depends on the size of your store file. If your
store file is small, invoke migratePersistentStore:toURL:options:withType:error: on your persistent
store coordinator, passing your existing persistent store and the URL returned from adding the iCloud-enabled
store to your persistent store coordinator.
If your store file is large, create a separate persistent store coordinator and managed object context that you
use to read from your existing store. Keep in mind that the amount of memory consumed during migration is
about twice the size of the store. Use the NSReadOnlyPersistentStoreOption option when you add the
existing persistent store to improve performance. Transfer data in batches to your iCloud-enabled managed
object context along with efficiently constructed fetch requests, setting the amount of data-per-batch with
the setFetchBatchSize: method. Each time you reach the size of the batch, save and reset the managed
object context.
See “Seeding Initial Data” (page 19) for more about importing data and “Detecting and Removing Duplicate
Records” (page 21) for more about deduplication.
Updating Your App to Take Advantage of iOS 7 and OS X 10.9
In iOS 7 and OS X 10.9, Core Data and iCloud are tightly integrated to simplify the work you do to integrate
your app with iCloud. Core Data now creates and manages a fallback store for you, long running processes
have been made asynchronous, and notifications provide more insight into the state of your app’s data. Consider
re-writing your persistent store setup and notification handler code to take advantage of these
improvements—you’ll find that your implementation is simpler, smaller, and more straightforward.
Because of the large improvements made to iCloud’s Core Data integration, the persistent store your app
created in previous versions of iOS and OS X is not compatible with iOS 7 and OS X 10.9. When you update
your app, use a different NSPersistentStoreUbiquitousContentNameKey to create a new iCloud-enabled
persistent store. Then migrate your user’s existing data from the old store to the new one. Take a look at
“Migrating User Data to iCloud,” because the steps you take to migrate from an old iCloud-enabled persistent
store are similar. The guidelines in “Performing Schema Migrations” (page 23) are useful as well—the behavior
of your app with different schema versions is just like the behavior of your app with different iCloud-enabled
store versions.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
32
Troubleshooting
With iCloud, users can create content on one device and finish creating it on another. Use these troubleshooting
steps when your app isn’t maintaining this seamless user experience. To learn about the iCloud debugging
tools available in Xcode that you’ll use along with these steps, read “Using the iCloud Debugging Tools” (page
35). For additional important debugging tips relevant to every iCloud-enabled app, see “Testing and Debugging
Your iCloud App” in iCloud Design Guide
Debugging Core Data Errors
To make it easier to find issues when you use Core Data with iCloud, simplify the architecture of your Core
Data stack—overcomplicating your implementation can make finding bugs difficult. Do this by following the
guidelines in “Using the SQLite Store with iCloud ” (page 7).
If your managed objects are behaving inconsistently, ensure that you are resetting your managed object
context(s) in your NSPersistentStoreCoordinatorStoresWillChangeNotification notification
handler and dropping any managed object references your app is holding. After receiving the
NSPersistentStoreCoordinatorStoresDidChangeNotification notification, you should refetch your
content.
If you are not receiving a notification you expect to receive, make sure that you have subscribed to these
notifications early enough. Many of Core Data’s iCloud-state-change notifications are posted during app launch
or soon after—for example, before adding the iCloud-enabled persistent store to your persistent store
coordinator.
Debugging Duplicate Data and Missing Data Issues
When your app loses data or creates multiple records, the root cause is usually related to how you migrated
or merged your data. You should test your app concurrently on multiple devices to determine what happens
when records are created or deleted. Review “Core Data Posts Content Changes from iCloud” (page 16) and
“Detecting and Removing Duplicate Records” (page 21) for implementation strategies.
If the app has duplicate records or can’t find a record to delete when you merge changes, you may have
registered for Core Data’s iCloud notifications in multiple places. Deregister view controllers that go offscreen
on iOS and inactive windows on OS X.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
33
Troubleshooting
Debugging Performance Problems
If the app has duplicate records when you migrate multiple peers, your deduplication algorithm is not
functioning or it does not correctly determine uniqueness. When your app runs migration on multiple devices,
they all need to pick the same record to keep and the same record(s) to delete.
If the app’s records are deleted when you migrate multiple peers, your deduplication algorithm does not
deterministically choose a winning record. When your app runs migration on multiple devices, they all need
to pick the same record to keep and the same record(s) to delete. Otherwise, each device will choose different
records and those decisions will all be persisted to iCloud.
Debugging Performance Problems
When you integrate your Core Data app with iCloud, follow the Core Data framework’s guidelines and best
practices to tune your app’s performance in the context of iCloud. As part of this effort, consider the content
that’s created on other devices while your app is running.
If your user interface fails to update when you import changes to content, check that you are correctly
dispatching back onto the main queue using the dispatch_async(3) function or an NSOperationQueue
queue. You can also configure your notification subscriptions to invoke your handler on the main queue. Recall
that managed objects are not thread-safe, so you might merge changes on a background managed object
context and then refetch on your main thread. If your change set is small, merge changes into a managed
object context on the main thread.
If your user interface freezes when you import changes to content, open the iCloud Report to find out when
your app receives changes from iCloud. Then look at the CPU Report and the Memory Report right after that
event—you should see a short spike in CPU and memory usage while Core Data imports the changes. A long
block of high CPU and memory usage is a sign that either the set of changes is too large or your app is receiving
many change sets in quick succession. To increase performance and decrease CPU and memory usage, you
can optimize when you save your managed object context. For optimization strategies, read “Core Data Posts
Content Changes from iCloud” (page 16) and “Best Practices” (page 30).
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
34
Using the iCloud Debugging Tools
Xcode, iOS, and OS X include tools that help you analyze and interpret changes in iCloud on your device and
in the cloud. Solve common integration problems using the information in this chapter along with the guidance
in “Troubleshooting” (page 33).
Understanding the iCloud Report
The iCloud Report gives you a detailed overview of your app’s ubiquity container. The Transfer Activity timeline
provides a broad overview of the changes being transferred over the network, and the Documents section
lists the status of individual files.
Note: OS X and iOS Simulator are “greedy peers.” Greedy peers upload and download data even
when your app isn’t running. So if you’re testing an iOS app, use a real device.
Interpreting Transfer Activity
The iCloud Report displays the bandwidth usage of iCloud over time for your app. Green bars indicate that
transaction logs or other miscellaneous requests have been sent from the currently attached peer to iCloud,
and blue bars indicate that transactions logs and requests have been received from iCloud. You use the Transfer
Activity timeline to:
●
Find out when your app is about to receive an iCloud event
●
Check that your app is properly communicating with iCloud
●
Optimize the frequency and timing of your managed object context saves
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
35
Using the iCloud Debugging Tools
Understanding the iCloud Report
Store Initialization
When you launch your app for the first time, Core Data and the system send metadata about your persistent
store to the cloud. This is displayed as a single green upload bar, as shown in Figure 5-1 below. As this happens,
Core Data will log a message containing “Using local storage: 1,” which means that your app is currently using
a temporary local persistent store.
Figure 5-1
Store initialization: Metadata uploaded
After iCloud finishes processing the request, Core Data creates an iCloud-enabled persistent store and transitions
to it. Core Data posts the notifications described in “iCloud Performs a One-Time Setup” (page 11) and logs a
message containing “Using local storage: 0,” which means that your app is currently using the iCloud-enabled
persistent store.
If the iCloud account already has records persisted from other peers, the system downloads the associated
transaction logs from iCloud in one or more requests, as shown in Figure 5-2 below.
Figure 5-2
Store initialization: Transaction logs downloaded
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
36
Using the iCloud Debugging Tools
Understanding the iCloud Report
Content Changes
Every time you save a managed object context, Core Data records the changes in a transaction log inside your
app’s ubiquity container. In the container, the transaction log is processed, and the file is uploaded to iCloud
as shown in Figure 5-3 below. Core Data notifies your app, too, as discussed in “Core Data Posts Content
Changes from iCloud” (page 16).
Figure 5-3
Content changes: Transaction log uploaded
Core Data creates a transaction log every time you save your managed object context to ensure atomicity.
Each transaction is uploaded from the ubiquity container to iCloud in a separate request to guarantee that
each transaction log is persisted wholly in a single operation. When you save too frequently, you increase the
work other peers do to import your changes. However, when you save too infrequently, large transaction logs
lead to increased memory usage. For example, in Figure 5-4 below, the large cluster of small green bars indicates
that your app is saving too often. You can read more about optimization in “Best Practices” (page 30).
Figure 5-4
Content changes: High save frequency
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
37
Using the iCloud Debugging Tools
Enabling iCloud Debug Logging
After other peers persist changes to iCloud, the associated transactions are downloaded to your app’s ubiquity
container, as shown in Figure 5-5 below.
Figure 5-5
Content changes: Transaction logs downloaded
Monitoring Document Status
The iCloud Report’s Documents section displays the status of files in your app’s local copy of the ubiquity
container, compared with those in the copy in the cloud. Table 5-1 describes these document states. The files
Core Data creates are an implementation detail, but you can generally infer the state of the device by comparing
the number of transaction logs on two devices and by monitoring file status changes.
Table 5-1
iCloud document status descriptions
Status
Description
Current
The most up-to-date version of the file is in iCloud.
Stored in Cloud
The file is in iCloud.
Data Not Present
The file is not yet in iCloud.
Excluded
The file is set not to upload to iCloud.
Has Revisions
The file has multiple versions.
Enabling iCloud Debug Logging
While the iCloud Report is typically sufficient, occasionally it can be helpful to take a look at more detailed
information. When you need in-depth iCloud logs, follow the steps below.
On iOS
1.
Install the iCloud Storage Debug Logging Profile on your iOS device.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
38
Using the iCloud Debugging Tools
Enabling iCloud Debug Logging
2.
Restart your device.
3.
Reproduce the issue.
4.
Sync your device with iTunes.
5.
Retrieve the iCloud device logs from
~/Library/Logs/CrashReporter/MobileDevice/device-name/DiagnosticLogs.
6.
Open the Settings app and delete the profile to turn off logging.
On OS X
1.
Open Terminal and run ubcontrol -k 7 to enable logging.
2.
Retrieve the iCloud logs from ~/Library/Logs/Ubiquity.
3.
Open Terminal and run ubcontrol -k 1 to turn off logging.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
39
Document Revision History
This table describes the changes to iCloud Programming Guide for Core Data .
Date
Notes
2014-07-15
Fixed the code listings for the deduplication algorithm.
2014-04-09
New document that describes how to use iCloud with Core Data.
2014-07-15 | Copyright © 2014 Apple Inc. All Rights Reserved.
40
Apple Inc.
Copyright © 2014 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrieval system, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, iTunes, Mac, OS X, and
Xcode are trademarks of Apple Inc., registered in
the U.S. and other countries.
iCloud is a service mark of Apple Inc., registered
in the U.S. and other countries.
IOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Times is a registered trademark of Heidelberger
Druckmaschinen AG, available from Linotype
Library GmbH.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.