iCloud Programming Guide for Core Data
Transcription
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.