Learning Core Data for iOS (2014)

14. iCloud

You never fail until you stop trying.

Albert Einstein

In Chapter 13, “Back Up and Restore with Dropbox,” a manual approach to storing data files “in the Cloud” was demonstrated using Dropbox. In this chapter, automatic data synchronization between user devices will be achieved using iCloud. Once Core Data is integrated with iCloud, changes made to application data on one user device will automatically be reflected on their other devices. At the time of writing, neither Dropbox nor iCloud allows data to be synchronized between accounts. If an application requires the ability for multiple accounts to synchronize with the same data, custom web service integration will be more appropriate than Dropbox or iCloud. Likewise, if an application requires ordered relationships, or needs a mapping model for migration in future releases, be aware that neither are supported with iCloud at this time.

This chapter covers the basics of Core Data integration with iCloud. You’ll first be shown how to enable the iCloud capability and then you’ll be shown just how easy it has become to integrate Core Data with iCloud since Xcode 5 and iOS 7. Once these are integrated, the new iCloud debugging features will be introduced, alongside tips for working with iCloud during the development phase. Finally, you’ll be shown how to allow users to opt out of using iCloud, even though they may be using an authenticated iCloud account.

Overview

iCloud is used to synchronize documents and data between devices belonging to one user. When Core Data is integrated with iCloud, its data is ubiquitous. The term ubiquitous means “available everywhere,” which is the fundamental intention of iCloud. For changes on one device to be reflected on another, each device needs to be signed in as the same iCloud user. The first device to use the application with iCloud will form a ubiquitous Core Data baseline for that application in iCloud. This baseline is the starting point used by other devices to build their own local copy of the application’s data from iCloud. This local copy of the application data is known as the iCloud Store. The iCloud Store is updated automatically according to change logs in the ubiquity container, which is maintained by Apple’s iCloud servers.

Three key components are involved when integrating Core Data with iCloud:

image The Application Stores Directory, also known as the application sandbox, is a local directory that currently holds the original Grocery-Dude.sqlite store. An iCloud.sqlite store will be added to this folder in a subfolder specific to each iCloud user. The per-user subfolders are managed transparently by Core Data. The folder structure is shown at the end of the chapter in the “Exercises” section.

image The Ubiquity Container is where iCloud documents and data specific to the authenticated iCloud user are found. Everything in this folder is synchronized automatically with the iCloud servers. If a directory with a .nosync suffix is stored in the ubiquity container, its contents will not be synchronized. You may have seen implementations of iCloud where the iCloud Store was placed in a .nosync folder of the ubiquity container. This approach is no longer recommended since iOS 7 because it prevents Core Data from transparently managing a Fallback Store. A Fallback Store is used to provide seamless transitions between iCloud accounts and reduce the time it takes for the iCloud Store to become usable for the first time. The Fallback Store is also used when the user is logged out of iCloud, or has disabled iCloud Documents & Data.

image iCloud is the name of the service allowing a user’s data to be synchronized across all of his or her devices. It is possible to see the contents of your iCloud container for debugging purposes at https://developer.icloud.com. You can also perform metadata queries to inspect the contents of iCloud or use the iCloud Debug Navigator introduced in Xcode 5, which will be shown later in the chapter. Each application using iCloud has its own directory at the root of iCloud, and applications from the same developer can be configured to share a ubiquity container within it. This is useful if you need to maintain separate free and paid versions of an application that need to access the same data.

Figure 14.1 shows an overview of the key components involved in Core Data and iCloud integration, along with the placement of stores and change logs.

Image

Figure 14.1 iCloud overview


Note

To continue building the sample application, you’ll need to have added the previous chapter’s code to Grocery Dude. Alternatively, you may download, unzip, and use the project up to this point from http://www.timroadley.com/LearningCoreData/GroceryDude-AfterChapter13.zip. Any time you start using an Xcode project from a ZIP file, it’s good practice to click Product > Clean. This practice ensures there’s no residual cache from previous projects using the same name.

You will also need to ensure that a valid developer profile has been selected as the Code Signing Identity for iCloud to work. You can set this in the Identity section of the General tab that is available when the Grocery Dude application target is selected.


Enabling iCloud

Prior to iOS 7, enabling iCloud required you to configure an App ID and provisioning profile entitled to use iCloud. Although this is still a requirement, it is handled for you automatically when you toggle the iCloud switch. This switch to enable iCloud is available on the Capabilities tab of the application target.

Update Grocery Dude as follows to enable the iCloud Capability:

1. Select the Capabilities tab of the Grocery Dude target, as shown in Figure 14.2.

Image

Figure 14.2 iCloud is off.

2. Ensure you’re connected to the Internet and turn on iCloud, as shown in Figure 14.3.

Image

Figure 14.3 iCloud is now on.

In the process of enabling iCloud, you’ll need to choose an appropriate Development Team. Once iCloud has been enabled, you should see an automatically generated ubiquity container name. You can set this container name to whatever you like, as long as you don’t change it once customers have data at this location. If you need data between free and paid versions of an application to remain consistent, you should ensure that the ubiquity container name in each application matches.

Updating CoreDataHelper for iCloud

To make it easy to reuse the sample code in your own projects, CoreDataHelper will be updated to include iCloud support. The first new method required is called iCloudAccountIsSignedIn and is used to check the iCloud account status. Only when an iCloud account has been authenticated will this method return YES. Listing 14.1 shows the code involved.

Listing 14.1 CoreDataHelper.m: iCloudAccountIsSignedIn


#pragma mark - ICLOUD
- (BOOL)iCloudAccountIsSignedIn {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
    if (token) {
        NSLog(@"** iCloud is SIGNED IN with token '%@' **", token);
        return YES;
    }
    NSLog(@"** iCloud is NOT SIGNED IN **");
    NSLog(@"--> Is iCloud Documents and Data enabled for a valid iCloud account on your Mac & iOS Device or iOS Simulator?");
    NSLog(@"--> Have you enabled the iCloud Capability in the Application Target?");
    NSLog(@"--> Is there a CODE_SIGN_ENTITLEMENTS Xcode warning that needs fixing? You may need to specifically choose a developer instead of using Automatic selection");
    NSLog(@"--> Are you using a Pre-iOS7 Simulator?");
    return NO;
}


Update Grocery Dude as follows to enable iCloud account status checks:

1. Add the following code to the bottom of CoreDataHelper.h before @end:

- (BOOL)iCloudAccountIsSignedIn;

2. Add the code from Listing 14.1 to the bottom of CoreDataHelper.m before @end.

3. Add the following code to the bottom of the didFinishLaunchingWithOptions method of AppDelegate.m before return YES;:

[[self cdh] iCloudAccountIsSignedIn];

4. Ensure your test iOS device or iOS Simulator is signed in to iCloud. The only mandatory iCloud setting you need to ensure is enabled is Documents & Data. You can check this via Settings > iCloud.

5. Run Grocery Dude on your test device and examine the top of the console log. Figure 14.4 shows the expected results; the token will vary.

Image

Figure 14.4 Signed in to iCloud

The iCloud Store

To hold a reference to the iCloud Store, a new property called iCloudStore will be added to the header of CoreDataHelper. Listing 14.2 shows the code involved.

Listing 14.2 CoreDataHelper.h


@property (nonatomic, readonly) NSPersistentStore *iCloudStore;


Update Grocery Dude as follows to add the iCloudStore property:

1. Add the property from Listing 14.2 to CoreDataHelper.h on the line after the existing sourceStore property is declared.

The iCloud Store content will be generated automatically based on change logs found in the ubiquity container of the authenticated iCloud user. The iCloud Store filename will be set to iCloud.sqlite; however, feel free to use any store name you think is appropriate in your own projects.

Update Grocery Dude as follows to configure the iCloud Store filename:

1. Add the following code to the bottom of the FILES section of CoreDataHelper.m:

NSString *iCloudStoreFilename = @"iCloud.sqlite";

The iCloud Store will be placed in a Stores directory within the application sandbox. For convenience, a new method will be added to the PATHS section of CoreDataHelper.m that returns the URL to the iCloud Store. Listing 14.3 shows the code involved.

Listing 14.3 CoreDataHelper.m: iCloudStoreURL


- (NSURL *)iCloudStoreURL {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:iCloudStoreFilename];
}


Update Grocery Dude as follows to implement the iCloudStoreURL method:

1. Add the code from Listing 14.3 to the bottom of the PATHS section of CoreDataHelper.m.

The next step is to implement the method that will load the iCloud Store. Listing 14.4 shows the code involved.

Listing 14.4 CoreDataHelper.m: loadiCloudStore


- (BOOL)loadiCloudStore {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    if (_iCloudStore) {return YES;} // Don't load iCloud store if it's already loaded

    NSDictionary *options =
    @{
      NSMigratePersistentStoresAutomaticallyOption:@YES
      ,NSInferMappingModelAutomaticallyOption:@YES
      ,NSPersistentStoreUbiquitousContentNameKey:@"Grocery-Dude"
    //,NSPersistentStoreUbiquitousContentURLKey:@"ChangeLogs" // Optional since iOS7
      };
    NSError *error;
    _iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                              configuration:nil
                                                        URL:[self iCloudStoreURL]
                                                    options:options
                                                      error:&error];
    if (_iCloudStore) {
        NSLog(@"** The iCloud Store has been successfully configured at '%@' **",
                                                          _iCloudStore.URL.path);
        return YES;
    }
    NSLog(@"** FAILED to configure the iCloud Store : %@ **", error);
    return NO;
}


Loading an iCloud Store has been greatly simplified since iOS 7 and the entire process can now be performed on the main thread. You still add a persistent store to the coordinator, similar to the approach used in the loadStore method. To use iCloud you pass an additional option key calledNSPersistentStoreUbiquitousContentNameKey. This key is a mandatory part of configuring Core Data integration with iCloud. Its value can be any string you would like to represent the ubiquitous store. Once Core Data has been integrated with iCloud, examining the contents ofhttps://developer.icloud.com/ will reveal this key’s usage as a directory name within the change logs.

Since iOS 7, what was a mandatory key for specifying the location of the change logs is now optional. This key, called NSPersistentStoreUbiquitousContentURLKey, should only be used when you need to manually control where the ubiquitous change logs are located—for example, in cases where your users have existing iCloud application data created on iOS 6.

Once all the persistent store options are set, they are given to the addPersistentStore method. This method returns immediately with a store the application can use. The store returned is actually the Fallback Store, which will be used transparently in lieu of the real iCloud Store until it is ready.

Update Grocery Dude as follows to implement loadiCloudStore:

1. Add the method from Listing 14.4 to the bottom of the ICLOUD section of CoreDataHelper.m.

iCloud Notifications

Once the iCloud Store is ready, a notification called NSPersistentStoreCoordinatorStoresWillChangeNotification will be sent to indicate that the store is about to change. Once it is changed, another notification called NSPersistentStoreCoordinatorStoresDidChangeNotification will be sent. Additionally, when a store imports ubiquitous content, a notification called NSPersistentStoreDidImportUbiquitousContentChangesNotification is sent. All of these notifications must be observed in order to trigger an appropriate response. Listing 14.5 shows the code involved in a new method called listenForStoreChanges, which is used to configure the application to observe these critical notifications.

Listing 14.5 CoreDataHelper.m: listenForStoreChanges


- (void)listenForStoreChanges {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
    [dc addObserver:self
           selector:@selector(storesWillChange:)
               name:NSPersistentStoreCoordinatorStoresWillChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(storesDidChange:)
               name:NSPersistentStoreCoordinatorStoresDidChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(persistentStoreDidImportUbiquitiousContentChanges:)
               name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
             object:_coordinator];
}


Update Grocery Dude as follows to listen for store changes:

1. Add the code from Listing 14.5 to the bottom of the ICLOUD section of CoreDataHelper.m. Xcode will now warn that the selectors that these notifications trigger are undeclared, which will be resolved shortly.

2. Add [self listenForStoreChanges]; to the bottom of the init method of CoreDataHelper.m before return self;.

The next step is to implement each of the methods that will be called when the store change notifications mentioned in Listing 14.5 are received. The code involved is shown in Listing 14.6.

Listing 14.6 CoreDataHelper.m: ICLOUD


- (void)storesWillChange:(NSNotification *)n {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    [_importContext performBlockAndWait:^{
        [_importContext save:nil];
        [self resetContext:_importContext];
    }];
    [_context performBlockAndWait:^{
        [_context save:nil];
        [self resetContext:_context];
    }];
    [_parentContext performBlockAndWait:^{
        [_parentContext save:nil];
        [self resetContext:_parentContext];
    }];

    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)storesDidChange:(NSNotification *)n {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)persistentStoreDidImportUbiquitiousContentChanges:(NSNotification*)n {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    [_context performBlock:^{
        [_context mergeChangesFromContextDidSaveNotification:n];
       [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                           object:nil];
    }];
}


As shown in Listing 14.6, there are three methods positioned to react to the three store change notifications previously shown in Listing 14.5:

image The storesWillChange method will be called just before the underlying persistent store is about to change. One scenario leading to this could be that the current iCloud account has changed. When this happens, the underlying store must change to another store containing the new user’s data. Because the old user might start using his or her account again in the future, this is an opportune time to save their data and reset each context. Because Grocery Dude has a context hierarchy, each context is synchronously saved and reset in order of child to parent. The calls in this method need to be synchronous because the old store is detached once the method returns.

image The storesDidChange method will be called once the underlying store has changed. This is an opportune time to refresh the user interface. This is achieved by sending the SomethingChanged notification that views in Grocery Dude already observe.

image The persistentStoreDidImportUbiquitiousContentChanges method is used to bring the managed object context up to speed with changes from iCloud. Whenever the underlying iCloud Store is updated, the notification that triggers this method is used to update the context driving the user interface.

Update Grocery Dude as follows to implement the methods used to respond to iCloud notifications:

1. Add the code from Listing 14.6 to the bottom of the ICLOUD section of CoreDataHelper.m.

The setupCoreData method of CoreDataHelper now needs to be updated to orchestrate the loading of an appropriate store. If the iCloud Store fails to load, the local store will be loaded instead. Listing 14.7 shows the code involved.

Listing 14.7 CoreDataHelper.m: loadiCloudStore


    if (![self loadiCloudStore]) {
        [self setDefaultDataStoreAsInitialStore];
        [self loadStore];
    }


Update Grocery Dude as follows to ensure an appropriate store is loaded:

1. Replace the existing code in the setupCoreData method of CoreDataHelper.m with the code from Listing 14.7.

2. Run the application and examine the console log. Figure 14.5 shows the expected results.

Image

Figure 14.5 Core Data and iCloud integration successfully configured

Since iOS 7, Core Data has taken on the responsibility of maintaining a Fallback Store. The Fallback Store allows the user to instantly begin using the application with iCloud, even if the network is unavailable. You can now expect to see two new entries in the console log, as follows:

image Using Local Storage 1 means that the Fallback Store is in use, so the user’s data won’t be available on other devices until the initial sync has completed.

image Using Local Storage 0 means that the transition to the iCloud Store is complete and the Fallback Store isn’t being used anymore. This is the message to look for that indicates iCloud synchronization is up and running. At this point, you should start seeing changes reflected on other devices using the same application with iCloud enabled.

You might not see the Using Local Storage 0 message immediately; however, provided you have seen Using Local Storage 1, then the persistent store is ready to work with. Later, when the background process responsible for completely bringing up the iCloud Store has completed, you should see Using Local Storage 0 in the console log.

The Debug Navigator

Since Xcode 5, greater visibility of iCloud activity has become possible. Using the Debug Navigator with a running iCloud application will reveal capacity, uploads, downloads, and individual document-level statuses. This lets you track the progress of the change logs and gives a better sense of what is going on under the hood. Figure 14.6 shows the Debug Navigator focused on iCloud.

Image

Figure 14.6 iCloud debugging

If the Debug Navigator informs you that iCloud is not configured, try enabling Documents & Data on your Mac in System Preferences > iCloud.

Disabling iCloud

Even though Grocery Dude now supports iCloud, it doesn’t mean that the user necessarily wants to use it. The user may wish to selectively disable iCloud for Grocery Dude yet still use iCloud with his or her other applications. To add this flexibility, a settings bundle with an iCloudEnabledkey will be added to allow the user to change his or her preference on using iCloud with Grocery Dude.

Update Grocery Dude as follows to implement the iCloudEnabled setting:

1. Select the Grocery Dude group and click File > New > File....

2. Click iOS > Resource > Settings Bundle and then click Next.

3. Ensure the Grocery Dude target and an appropriate directory have been selected, and then click Create.

4. Expand Settings.bundle and select Root.plist.

5. Configure the Root.plist as shown in Figure 14.7.

Image

Figure 14.7 Settings bundle

To get the current value of the iCloudEnabled key, a new method will be added to CoreDataHelper.m called iCloudEnabledByUser. As shown in Listing 14.8, this method retrieves the current value of the iCloudEnabled key and returns an equivalent BOOL value.

Listing 14.8 CoreDataHelper.m: iCloudEnabledByUser


- (BOOL)iCloudEnabledByUser {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    [[NSUserDefaults standardUserDefaults] synchronize]; // Ensure current value
    if ([[[NSUserDefaults standardUserDefaults]
                          objectForKey:@"iCloudEnabled"] boolValue]) {
        NSLog(@"** iCloud is ENABLED in Settings **");
        return YES;
    }
    NSLog(@"** iCloud is DISABLED in Settings **");
    return NO;
}


Update Grocery Dude with code to check the user’s iCloud preference as follows:

1. Add the code from Listing 14.8 to the bottom of the ICLOUD section of CoreDataHelper.m.

In your own applications, you may wish to present the user with an alert view asking whether he or she would like to use iCloud. To keep this chapter as succinct as possible, users of Grocery Dude will need to use the Settings App to enable iCloud. Because this setting can only change when the application enters the background, the user’s preference on using iCloud will be checked each time the application enters the foreground. If an authenticated iCloud user has opted to disable iCloud support in Grocery Dude, the Core Data stack will need to be reset and the non-iCloud store loaded instead. Listing 14.9 shows the code involved in resetting the Core Data stack.

Listing 14.9 CoreDataHelper.m: CORE DATA RESET


- (void)removeAllStoresFromCoordinator:(NSPersistentStoreCoordinator*)psc {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    for (NSPersistentStore *s in psc.persistentStores) {
        NSError *error = nil;
        if (![psc removePersistentStore:s error:&error]) {
            NSLog(@"Error removing persistent store: %@", error);
        }
    }
}
- (void)resetCoreData {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    [_importContext performBlockAndWait:^{
        [_importContext save:nil];
        [self resetContext:_importContext];
    }];
    [_context performBlockAndWait:^{
        [_context save:nil];
        [self resetContext:_context];
    }];
    [_parentContext performBlockAndWait:^{
        [_parentContext save:nil];
        [self resetContext:_parentContext];
    }];
    [self removeAllStoresFromCoordinator:_coordinator];
    _store = nil;
    _iCloudStore = nil;
}


The removeAllStoresFromCoordinator method removes each store found in the given coordinator. The resetCoreData method saves and resets each context and then uses removeAllStoresFromCoordinator to remove all stores from the _coordinator. The final step involves setting each store tonil so the Core Data stack is ready to be set up again.

Update Grocery Dude as follows to implement the remainder of the CORE DATA RESET section:

1. Add the code from Listing 14.9 to the bottom of the existing CORE DATA RESET section of CoreDataHelper.m.

As the application becomes active, a check needs to be performed to ensure that the appropriate store is loaded. Listing 14.10 shows the code involved in a new method called ensureAppropriateStoreIsLoaded that will perform this task.

Listing 14.10 CoreDataHelper.m: ensureAppropriateStoreIsLoaded


- (void)ensureAppropriateStoreIsLoaded {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    if (!_store && !_iCloudStore) {
        return; // If neither store is loaded, skip (usually first launch)
    }
    if (![self iCloudEnabledByUser] && _store) {
        NSLog(@"The Non-iCloud Store is loaded as it should be");
        return;
    }
    if ([self iCloudEnabledByUser] && _iCloudStore) {
        NSLog(@"The iCloud Store is loaded as it should be");
        return;
    }
    NSLog(@"** The user preference on using iCloud with this application appears to have changed. Core Data will now be reset. **");

    [self resetCoreData];
    [self setupCoreData];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:
    @"Your preference on using iCloud with this application appears to have changed"
                                                    message:
    @"Content has been updated accordingly"
                                                   delegate:nil
                                          cancelButtonTitle:nil
                                          otherButtonTitles:@"Ok", nil];
    [alert show];
}


The ensureAppropriateStoreIsLoaded method is quite straightforward. If neither store is loaded, the method returns early. This will happen when the application is launching for the first time. If iCloud has been disabled and the non-iCloud _store is loaded, or iCloud is enabled and_iCloudStore is loaded, the method will also return early. Anything else is considered a discrepancy and Core Data is consequently reset, set up, and the views and user notified.

Update Grocery Dude as follows to ensure the appropriate store is loaded each time the application becomes active:

1. Add the code from Listing 14.10 to the bottom of the ICLOUD section of CoreDataHelper.m.

2. Add the following code to the bottom of CoreDataHelper.h before @end:

- (void)ensureAppropriateStoreIsLoaded;

3. Add the following code to the bottom of the applicationWillEnterForeground method of AppDelegate.m:

[[self cdh] ensureAppropriateStoreIsLoaded];

Even though a check is now in place to ensure that an appropriate store is loaded when the application enters the foreground, the setupCoreData method needs to be updated to ensure that an appropriate store is loaded during setup. This determination is made based on the user’s iCloudEnabledpreference. Listing 14.11 shows the code involved in an updated setupCoreData method.

Listing 14.11 CoreDataHelper.m: setupCoreData


- (void)setupCoreData {
if (debug==1) {
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
    if (!_store && !_iCloudStore) {
        if ([self iCloudEnabledByUser]) {
            NSLog(@"** Attempting to load the iCloud Store **");
            if ([self loadiCloudStore]) {
                return;
            }
        }
        NSLog(@"** Attempting to load the Local, Non-iCloud Store **");
        [self setDefaultDataStoreAsInitialStore];
        [self loadStore];
    } else {
      NSLog(@"SKIPPED setupCoreData, there's an existing Store:\n ** _store(%@)\n ** _iCloudStore(%@)", _store, _iCloudStore);
    }
}


The updated setupCoreData method begins by checking whether there’s an existing _store or _iCloudStore. If there is, the method returns early; otherwise, it checks the user’s preference on using iCloud and loads an appropriate store in response. If the iCloud Store load fails, the non-iCloud store is loaded instead.

Update Grocery Dude as follows to implement the modified setupCoreData method:

1. Replace the existing setupCoreData method in CoreDataHelper.m with the method from Listing 14.11.

2. Run the application on a test device. The local, non-iCloud store should load and be populated with default data.

3. Press the Home button and open the Settings App.

4. Scroll down and select Grocery Dude; then enable iCloud as shown in Figure 14.8.

Image

Figure 14.8 Enable iCloud for Grocery Dude

5. Return to the Grocery Dude app and you should see the data set change, as shown in Figure 14.9.

Image

Figure 14.9 iCloud Preference Change

It is important to realize that there are now two ways to disable iCloud, each having a different result:

image If iCloud is disabled by turning Settings > Grocery Dude > Enable iCloud off, then the local, non-iCloud Store will be used.

image If iCloud is disabled by turning iCloud > Documents & Data > Grocery Dude off, then the Fallback Store will be used transparently.

If an app isn’t already on the App Store, or there’s no need to cater for existing customer persistent stores, then you don’t need the Settings.bundle or the [self iCloudEnabledByUser] check in the setupCoreData method of CoreDataHelper.m. Removing this check will prevent the non-iCloud store from ever being used, and will always provide the user with either an iCloud store or Fallback Store. Regardless of whether the user enables or disables iCloud Documents & Data, his or her data will always be available. If you use this approach, features such as Dropbox backup shouldn’t be used and will need to be disabled, which is discussed in the next chapter.

Summary

Core Data has now been integrated with iCloud and yet there’s still work to do to ensure it behaves in a production-like manner. You may find that the initial sync time will vary, depending on the state of the change logs that have to be replayed and the speed of the network connection. Because iCloud provides background synchronization, changes made on one device may take a while to show up on other devices. One key point to remember is that unsaved changes in a context won’t appear on other devices. Frequent saves will ensure that changes show up on other devices as fast as possible. The following chapter demonstrates techniques such as de-duplication and seeding, which will ensure that the user experience is a good one. It is recommended that you complete the next chapter prior to performing any further rigorous testing with iCloud.

Exercises

Why not build on what you’ve learned by experimenting?

1. Examine your iCloud contents by logging in to https://developer.icloud.com with your developer account.

2. Test iCloud debug logging as follows:


Warning

This is incredibly verbose, so disable it when you’re finished testing it.


a. Click Product > Scheme > Edit Scheme....

b. Ensure Run Grocery Dude... and the Arguments tab are selected.

c. Click the + in the Arguments Passed On Launch section.

d. Type in -com.apple.coredata.ubiquity.logLevel 3 and then click OK.

e. Run the application again and examine the console log.

3. iCloud integration will now maintain a file structure in the application sandbox to cater to multiple iCloud accounts. Examine the contents of the Grocery Dude sandbox as follows:

a. Ensure your iOS device is connected and has run Grocery Dude previously with iCloud enabled and signed in.

b. Click Window > Organizer in Xcode.

c. Select the Applications subfolder beneath your device.

d. Select the Grocery Dude application and then click Download and save the filename.xcappdata file to your Documents folder.

e. Navigate to the downloaded file in Finder and then right-click it and select Show Package Contents.

f. Expand the folder structure as shown in Figure 14.10.

Image

Figure 14.10 Core Data’s application sandbox structure

As shown in Figure 14.10, the folder structure leading to iCloud.sqlite is different from the one you’ve specified in the iCloudStoreURL method. The additional structure has been inserted by Core Data to allow seamless support for multiple iCloud accounts. The structure varies depending on whether or not you’re using local storage. If you have an additional iCloud account, try using it with Grocery Dude to see the additional store and folders that are generated.