With iOS 5, Apple introduced iCloud, the latest in its line of Internet-based tools and services. To the end user, iCloud extends Apple’s previous MobileMe offerings of e-mail, contact management, and Find My iPhone, with iOS backup and restore, iTunes Match, Photo Stream, and Back to My Mac. With iOS 8, iCloud got a major overhaul and has included a new framework called CloudKit. This provides you with authentication, private and public database structures, and asset storage services.

For all the bells and whistles that Apple has built, at its heart iCloud is a cloud-based storage and synchronization service. Its main purpose is to allow users to access their content across all their devices: iPhone, iPad, or Mac. In addition, Apple has given iOS developers a set of application programming interfaces (APIs) for accessing iCloud. This lets you build apps that can take advantage of the same iCloud features as Apple without having to invest in building an extensive server infrastructure. Even better, you don’t have to learn a new complicated software development kit (SDK). Rather than providing a new iCloud framework, Apple added new classes to existing frameworks, primarily Foundation and UIKit, and extended existing classes to enable iCloud access.

The basic idea behind iCloud is to have a single place where apps can store and access data. Changes made by one instance of your app on one device can be instantly propagated to another instance of the app running on another device. At the same time, iCloud provides an authoritative copy of your application’s data. This data can be used to restore your application’s state on a new device, providing a seamless user experience as well as backup data. The data with CloudKit is separate and is public data that is shared between all users of their apps. This data is stored like on a shared disk drive that can be accessed from other devices as well.

Data Storage with iCloud

There are a few different ways to store your data in iCloud.

  • iOS Backup: This is a global device configuration that backs up your iOS device to iCloud.

  • Key-value data storage: This is used for storing small amounts of infrequently changing data used by your application.

  • Document storage: This is used for storing user documents and application data.

  • Core Data with iCloud: This puts your application’s persistent backing store on iCloud.

  • iCloud Drive: This makes your iCloud like an online drive for synchronizing your data files.

Before we discuss these storage mechanisms in detail, let’s review how iCloud and iOS work together.

iCloud Basics

Inside your iCloud application there is a ubiquity container. Depending on the storage type used, you may explicitly define the URL for this container or iOS will create one for you. The ubiquity container is where iCloud data is stored by your application. iOS will synchronize the data between your device and iCloud. This means that any changes your application makes to data in the ubiquity container will be sent to iCloud. Conversely, any changes in iCloud will be sent to your application’s ubiquity container on your device.

Now, iOS doesn’t send the entire data file back and forth from iCloud for every change. Internally, iOS and iCloud break up your application’s data into smaller chunks of data. When changes occur, only the chunks that have changed are synchronized with iCloud. On iCloud, your application data is versioned, keeping track of each set of changes.

In addition to breaking up your application’s data into chunks, iOS and iCloud will send the data file’s metadata. Since the metadata is relatively small and important, the metadata is sent all the time. In fact, iCloud will know a data file’s metadata before the actual data is synchronized. This is especially important with iOS. Since an iOS device may be space and bandwidth constrained, iOS won’t necessarily automatically download data from iCloud until it needs it. But since iOS has the metadata, it knows when its copy is out of date with iCloud.

Note

Interestingly, if iOS detects another iOS device on the same WiFi network, rather than sending data up to iCloud and down to the other device, iOS will simply transfer the data from one device to the other.

iCloud Backup

Backup is an iOS system service offered by iCloud. It automatically backs up your iOS device daily over WiFi. Everything in your application’s home directory is backed up. The application bundle, caches directory, and temp directory are ignored by iOS. Since the data is transmitted over WiFi and sent to Apple’s iCloud data center, you should try to keep your application’s data as small as possible. The more data, the longer the backup time and the more iCloud storage your users will consume.

Note

If you’ve used up your iCloud storage capacity (at the time of this writing, 5GB by default), iOS will ask you if you want to buy more storage. Regardless, you’ll need to figure out how your application will handle the case if iCloud is full.

When designing your application’s data storage policy, keep the following in mind:

User-generated data, or data that cannot be re-created by your application, should be stored in the Documents directory. From there it will be automatically backed up to iCloud.

Data that can be downloaded or re-created by your application should live in Library/Caches.

Data that is temporary should be stored in the tmp directory. Remember to delete these files when they are no longer needed.

Data that your application needs to persist, even in low storage situations, should be flagged with the NSURLIsExcludedFromBackupKey attribute. Regardless of where you put these files, they will not be deleted by Backup. It’s your application’s responsibility to manage these files.

You can set NSURLIsExcludedFromBackupKey via the setResource:forKey:error: method in NSURL.

var url:NSURL? = NSBundle.mainBundle().URLForResource("NoBackup", withExtension: "txt")?

var error: NSError? = nil

var success = url?.setResourceValue(true, forKey: NSURLIsExcludedFromBackupKey, error: &error)

Enabling iCloud in Your Application

To use iCloud data storage within your application, you need to perform two tasks. First, you need to enable the application’s entitlements and enable them for iCloud. Second, you need to create an iCloud-enabled provisioning profile. In the past, this was a bit tedious; now you can simply choose the capabilities from within Xcode and enable iCloud and the services you need. If you have configured your Developer Account, Xcode will generate the relevant IDs and entitlements and certificates as required. This is such a wonderful feature.

When entitlements are enabled in your application, Xcode expects to find an .entitlements file within your project directory. This .entitlements file is simply a property list of key-values pairs. These key-value pairs configure additional capabilities or security features of your application. For iCloud access, the .entitlements file specifies the keys to define ubiquity identifiers for the iCloud key-value and document ubiquity containers.

Key-Value Data Storage

As the name suggests, iCloud key-value data storage is a simple key-value storage mechanism integrated with iCloud. Conceptually, it’s similar to NSUserDefaults. Like NSUserDefaults, the only allowable data types are those supported by property lists. It is best to use it for data with values that are infrequently updated. Placing your application’s preferences or settings would be a good use case. You shouldn’t use key-value data storage in place of NSUserDefaults. You should keep writing configuration information to NSUserDefault and write shared data to key-value data storage. This way, your application still has configuration information if iCloud is unavailable.

There are a number of limitations on the key-value data storage that you need to keep in mind. First, there is a 1MB maximum storage limit per value. Keys have a separate 1MB per-key limit. Furthermore, each application is allowed a maximum of 1,024 separate keys. As a result, you will need to be judicious about what you put in key-value data storage.

Key-value data is synced with iCloud at periodic intervals. The frequency of these intervals is determined by iCloud, so you don’t have much control over this. As a result, you shouldn’t use the key-value data storage for time-sensitive data.

Key-value data storage handles data conflicts by always choosing the latest value for each key.

To use key-value data storage, you use the default NSUbiquitousKeyValueStore. You access values using the appropriate *ForKey: and set*ForKey: methods, similar to NSUserDefaults. You will also need to register for notifications about changes to the store via iCloud. To synchronize data changes, you call the synchronize method. You can also use the synchronize method as a check to see whether iCloud is available. You might initialize your application to use key-value data storage like this:

    var kv_store = NSUbiquitousKeyValueStore.defaultStore()

    NSNotificationCenter.defaultCenter().addObserver(self,

                    selector: "storeDidChange:",

                        name: NSUbiquitousKeyValueStoreDidChangeExternallyNotification,

                      object: self.kv_store)

    var avail = self.kv_store.synchronize()

    if avail {

        println("iCloud is available")

    } else {

        println("iCloud NOT available")

    }

}

The synchronize method does not push data to iCloud. It simply notifies iCloud that new data is available. iCloud will determine when to retrieve the data from your device.

You can save a key onto iCloud with set*ForKey: like so:

self.kv_store.setString("Hello", forKey: "World")

To access the data from iCloud, you can use *ForKey: like so:

var result = self.kv_store.stringForKey("World") as String?

Document Storage

For iCloud document storage, a document is a custom subclass of UIDocument. UIDocument is an abstract class that is used to store your application’s data, either as a single file or as a file bundle. A file bundle is a directory that behaves as a single file. To manage a file bundle, use the NSFileWrapper class.

Before we describe iCloud Document Storage, let’s look at UIDocument.

UIDocument

UIDocument eases the development of document-based applications by giving a number of features for “free.”

  • Background reading and writing of data: Keeps your application’s UI responsive

  • Conflict detection: Helps you resolve differences between document versions

  • Safe-saving: Makes sure your document is never in a corrupted state

  • Automated saves: Makes life easier for your users

  • Automatic iCloud integration: Handles all interchanges between your document and iCloud

If you want to build a single file document, you would create a simple UIDocument subclass.

class myDocument: UIDocument {

    var text: String!

}

You need to implement a number of methods in your UIDocument subclass. First, you need to be able to load the document data. To do this, you override the loadFromContents:ofType:error: method.

override func loadFromContents(contents: AnyObject, ofType typeName: String, error outError: NSErrorPointer) -> Bool {

    if contents.length > 0 {

        self.text = NSString(data: contents as NSData, encoding: NSUTF8StringEncoding)

    } else {

        self.text = ""

    }

    //Update here

    return true

}

The contents parameter is defined as AnyObject. If your document is a file bundle, the content will be of type NSFileWrapper. For your single document file case, the content is an NSData object. This is a simple implementation; it never fails. If you implemented a failure case and returned false, you should create an error object and give it a meaningful error message. You also want to put code in place to update the UI once the data is successfully loaded. You also never check the content type. Your application could support multiple data types, and you have to use the typeName parameter to handle the different data loading scenarios.

When you close your application or when auto-save is invoked, the UIDocument method contentForType:error: is called. You need to override this method as well.

override func contentsForType(typeName: String, error outError: NSErrorPointer) -> AnyObject? {

    if self.text == nil {

        self.text = ""

    }

    var data = self.text.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

    return data

}

If your document is stored as a file bundle, you return an instance of NSFileWrapper rather than the NSData object for a single file. That’s all you need to do to ensure your data gets saved; UIDocument will handle the rest.

UIDocument needs a file URL to determine where to read and write data. The URL will define the document directory, file name, and possibly file extension. The directory can be either a local (application sandbox) directory or a location in the iCloud ubiquity container. The file name should be generated by your application, optionally allowing the user to override the default value. While using a file extension might be optional, it’s probably a good idea to define one (or more) for your application. You pass this URL to the fileURL: method of your UIDocument subclass to create a document instance.

    var doc = myDocument(fileURL: aURL)

...

    doc.saveToURL(fileURL, forSaveOperation: UIDocumentSaveOperation.ForCreating, completionHandler: {

        success in

        if (success){

        } else {

        }

})

Once you have created a UIDocument instance, you create the file using the saveToURL:forSaveOperation:completionHandler: method. You use the value UIDocumentSaveForCreating to indicate that you are saving the file for the first time. The completionHandler: parameter takes a block. The block takes a Bool parameter to tell you whether the save operation was successful.

You don’t just need to create documents; your application may need to open and close existing documents. You still need to call initWithFileURL: to create a document instance, but then you call openWithCompletionHandler: and closeWithCompletionHandler: to open and close your document.

    var doc = myDocument(fileURL: aURL)

...

    doc.openWithCompletionHandler({

        success in

        if (success) {

        } else {

        }

    })

    doc.closeWithCompletionHandler(nil)

Both methods take a block to execute on completion. Like the saveToURL:forSaveOperation:completionHandler: method, the block has a Bool parameter to tell you whether the open/close succeeded or failed. You’re not required to pass a block. In the previous example code, you pass nil to closeWithCompletionHandler: to indicate you don’t do anything after the document is closed.

To delete a document, you could simply use NSFileManager removeItemAtURL: and pass in the document file URL. However, you should do what UIDocument does for reading and writing and perform the delete operation in the background.

    var doc = myDocument(fileURL: aURL)

...

// close the document

...

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

        var fileCoordinator = NSFileCoordinator(filePresenter: nil)

        fileCoordinator.coordinateWritingItemAtURL(aURL,

                          options: NSFileCoordinatorWritingOptions.ForDeleting,

                            error: nil,

         byAccessor: {

            writingURL in

            var fileManager = NSFileManager()

            fileManager.removeItemAtURL(writingURL, error: nil)

        })

    })

First, you dispatch the entire delete operation to a background queue via the dispatch_async function. Inside the background queue, you create an NSFileCoordinator instance. NSFileCoordinator coordinates file operations between processes and objects. Before any file operation is performed, it sends messages to all the NSFilePresenter protocol objects that have registered themselves with the file coordinator. Delete the document file by invoking the NSFileCoordinator method coordinateWritingItemAtURL:options:error:by Accessor:. The accessor is a block operation that defines the actual file operation you want performed. It’s passed an NSURL parameter, representing the location of the file. Always use the block parameter NSURL, not the NSURL passed to coordinateWritingItemAtURL:.

Before performing an operation on your UIDocument subclass, you probably want to check the documentState property. The possible states are defined as follows:

  • UIDocumentState.Normal: The document is open and has no issues.

  • UIDocumentState.Closed: The document is closed. If the document is in this state after opening, it indicates there may be a problem with the document.

  • UIDocumentState.InConflict: There are versions of this document in conflict. You may need to write code to allow your user to resolve these conflicts.

  • UIDocumentState.SavingError: The document could not be saved because of some error.

  • UIDocumentState.EditingDisabled: The document cannot be edited; either your application or iOS will not permit it.

You can check the document state.

var doc = myDocument(fileURL: aURL)

...

if (doc.documentState == UIDocumentState.Closed) {

    // documentState is closed

}

UIDocument also provides a notification named UIDocumentStateChangedNotification that you can use to register an observer.

MyDocument *doc = [[MyDocument alloc] initWithFileURL:aURL]];

...

NSNotificationCenter.defaultCenter().addObserver(anObserver,

                   selector: "documentStateChanged:",

                       name: UIDocumentStateChangedNotification,

                     object: doc)

Your observer class would implement the method documentStateChanged: to check the document state and handle each state accordingly.

To perform automated saves, UIDocument periodically invokes the method hasUnsavedChanges, which returns a Bool depending on whether your document has changed since the last save. The frequency of these calls is determined by UIDocument and cannot be adjusted. Generally, you don’t override hasUnsavedChanges. Rather, you do one of two things: register the NSUndoManager via the UIDocument undoManager property to register for undo/redo operations or call the updateChangeCount: method every time a trackable change is made to your document. For your document to work with iCloud, you must enable the automated saves feature.

UIDocument with iCloud

Using iCloud document storage requires an adjustment to the normal UIDocument process to use a Documents subdirectory of your application’s ubiquity container. To get the ubiquity container URL, you pass the document identifier into the NSFileManager method URLForUbiquityContainerIdentifer:, passing nil as the argument.

let dataDir = "Documents"

var fileManager = NSFileManager.defaultManager()

var iCloudToken = fileManager.ubiquityIdentityToken

var iCloudURL: NSURL? = fileManager.URLForUbiquityContainerIdentifier(nil)

if (iCloudToken != nil && iCloudURL != nil) {

    var ubiquityDocURL = iCloudURL?.URLByAppendingPathComponent("Documents")

} else {

    // No iCloud Access

}

By using nil in URLForUbiquityContainerIdentifer:, NSFileManager will use the ubiquity container ID defined in the application’s entitlements file. We’ll cover this in the “Entitlements” section later in the chapter, but for now, just try to follow along. If you want to use the ubiquity container identifier explicitly, it’s a combination of your ADC Team ID and App ID.

let ubiquityContainer = "HQ7JAY4x53.com.ozapps.iCloudAppID"

let fileManager = NSFileManager.defaultManager()

let ubiquityURL = fileManager.URLForUbiquityContainerIdentifier(ubiquityContainer)

Notice the use of the NSFileManager method ubiquityIdentityToken to check for iCloud availability. This method returns a unique token tied to the user’s iCloud account. Depending on your application, if iCloud access is unavailable, you should inform the user and either work with local storage or exit the application.

NSMetadataQuery

Earlier, we stated that iCloud and iOS don’t automatically sync documents in an application’s ubiquity container. However, a document’s metadata is synced. For an iCloud document storage application, you can’t simply use the file contents of the Documents directory in your ubiquity container to know what documents are available for your application. Rather, you have to perform a metadata query using the NSMetadataQuery class.

Early in your application life cycle you need to instantiate an NSMetadataQuery and configure it to look for the appropriate documents in the Documents subdirectory of the ubiquity container.

self.query:NSMetadataQuery = NSMetadataQuery()

self.query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]

var filePattern = "*.txt"

self.query.predicate = [NSPredicate(format: "%K LIKE %@", NSMetadataItemFSNameKey, filePattern)]

This example assumes you have a query property and it’s configured to look for all files with the .txt extension.

After creating the NSMetadataQuery object, you need to register for its notifications.

var notificationCenter = NSNotificationCenter.defaultCenter()

notificationCenter.addObserver(self, selector: "processFiles:", name: NSMetadataQueryDidFinishGatheringNotification, object: nil)

notificationCenter.addObserver(self, selector: "processFiles:", name: NSMetadataQueryDidUpdateNotification, object: nil)

self.query.startQuery()

NSMetadataQueryDidFinishGatheringNotification is sent when the query object has finished its initial information-loading query. NSMetadataQueryDidUpdateNotification is sent when the contents of the Documents subdirectory have changed and affect the results of the query. Finally, you start the query.

When a notification is sent, the processFiles: method is invoked. It might look something like this:

func processFiles(aNotification:NSNotification){

        var query = NSMetadataQuery()

        var files:[AnyObject?] = []

        query.disableUpdates()

        var queryResults = query.results

        for result in queryResults {

            var fileURL = result.valueForAttribute(NSMetadataItemURLKey) as NSURL

            var aBool: AnyObject?

            fileURL.getResourceValue(&aBool, forKey: NSURLIsHiddenKey, error: nil)

            if let hidden = aBool as? Bool {

                if (!hidden) {

                    files.append(fileURL)

                }

            }

        }

        query.enableUpdates()

    }

First, you disable the query updates to prevent notifications from being sent while you’re processing. In this example, you simply get a list of files in the Documents subdirectory and add them to an array. You make sure to exclude any hidden files in the directory. Once you have the array of files, you use them in your application (perhaps to update a table view of file names). Finally, you reenable the query to receive updates.

You’ve only skimmed the surface of how to use iCloud document storage. There are a lot of document life-cycle issues that your document-based application should handle to be effective.

Note

For more information, read Apple’s documentation. Check the iCloud chapter of the iOS App Programming Guide first ( https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesignForCoreDataIniCloud.html#//apple_ref/doc/uid/TP40012094-CH3-SW1 ). Then read the iCloud Design Guide ( https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html ) and the document-based App Programming Guide for iOS ( https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForDocumentsIniCloud.html#//apple_ref/doc/uid/TP40012094-CH2-SW1 ).

Core Data with iCloud

Using Core Data with iCloud is a fairly simple process. You place your persistent store in your application’s ubiquity container. However, you don’t want your persistent store to be synchronized with iCloud. That would create unnecessary overhead. Rather, you want to synchronize the transactions between applications. When another instance of your application receives the transaction data from iCloud, it reapplies every operation performed on the persistent store. This helps ensure that the different instances are updated with the same set of operations.

Even though you don’t want to synchronize the persistent store with iCloud, Apple recommends that you place the data file in the ubiquity container within a folder with the extension .nosync. This tells iOS not to synchronize the contents of this folder but keeps the data associated with the correct iCloud account.

var psc = _persistentStoreCoordinator

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

    var newStore: NSPersistentStore? = nil

    var error: NSError? = nil

    //Assume we have an instance of NSPersitentStoreCoordinator _psesistentStoreCoordinator

    let dataFile = "SuperDB.sqlite"

    let dataDir = "Data.nosync"

    let logsDir = "Logs"

    var fileManager = NSFileManager.defaultManager()

    var ubiquityToken = fileManager.ubiquityIdentityToken

    var ubiquityURL: NSURL? = fileManager.URLForUbiquityContainerIdentifier(nil)

    if (ubiquityToken != nil && ubiquityURL != nil) {

        var dataDirPath = ubiquityURL?.path?.stringByAppendingPathComponent(dataDir)

        if fileManager.fileExistsAtPath(dataDirPath!) == false {

            var fileSystemError: NSError? = nil

            fileManager.createDirectoryAtPath(dataDirPath!,

                              withIntermediateDirectories: true,

                                               attributes: nil,

                                                    error: &fileSystemError)

            if fileSystemError != nil {

                println("Error creating database directory \(fileSystemError)")

            }

        }

        var thePath = ubiquityURL?.path?.stringByAppendingPathComponent(logsDir)

        var logsURL = NSURL(fileURLWithPath: thePath!)

        var options = NSMutableDictionary()

            options[NSMigratePersistentStoresAutomaticallyOption] = true

            options[NSInferMappingModelAutomaticallyOption] = true

            options[NSPersistentStoreUbiquitousContentNameKey] = ubiquityURL?.lastPathComponent!

            options[NSPersistentStoreUbiquitousContentURLKey] = logsURL!

        psc.lock()

        thePath = dataDirPath?.stringByAppendingPathComponent(dataFile)

        var dataFileURL = NSURL.fileURLWithPath(thePath!)

        newStore = psc.addPersistentStoreWithType(NSSQLiteStoreType,

                           configuration: nil,

                                     URL: dataFileURL,

                                 options: options,

                                   error: &error)

        psc.unlock()

    } else {

        println("Local Store")

    }

})

Notice that you perform your persistent store operations in a background queue so that your iCloud access does not block your application UI. Most of the example here defines your data directory path, Data.nosync, and the log directory path, Logs. The actual persistent store creation is similar to what you’ve done earlier. You added two key-value pairs to the options dictionary: NSPersistentStoreUbiquitousContentNameKey with your ubiquity container ID and NSPersistentStoreUbiquityContentURLKey with the transaction log directory path. Core Data and iCloud will use NSPersistentStoreUbiquityContentURLKey to synchronize the transaction logs.

Now you need to register to observe a notification when changes are received from iCloud. Generally, you don’t want to put this when you create the persistent store coordinator; rather, you do it when creating the managed object context.

NSNotificationCenter.defaultCenter().addObserver(self,

    selector: "mergeChangesFromUbiquitousContent:",

        name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,

      object: coordinator)

The implementation of mergeChangesFromUbiquitousContent: will have to handle the merging of content between iCloud and the local persistent store. Fortunately, for all but the most complicated models, Core Data makes this relatively painless.

func mergeChangesFromUbiquitousContent(notification: NSNotification) {

    var context = self.managedObjectContext

    context?.performBlock({

        context?.mergeChangesFromContextDidSaveNotification(notification)

        // Send a notification to refresh the UI, if necessary

    })

}

Enhancing SuperDB

You’ll enhance the Core Data SuperDB application and place the persistent store in iCloud. Based on your review of the iCloud APIs, this should be a fairly straightforward process. Remember, you can’t run iCloud apps on the simulator (yet), so you need to tether your device to your development machine. Additionally, since you need a provisioning profile, you need an Apple Developer Center account.

Make a copy of the SuperDB project from Chapter 6. If you haven’t completed Chapter 6, you can copy the project from this book’s download archive and start from there.

Entitlements

You will need an entitlements file for your applications. Earlier you had to create this file yourself and set up the key-value entries. Figure 8-1 shows the entitlements file for the SuperDB app. To create this file, you just have to switch on a required capability and the entitlements file is automatically created by Xcode. You will do that in the next section.

Figure 8-1.
figure 1figure 1

The entitlements section of the target Summary Editor

Enabling iCloud and Creating the Relevant Files

You need to create the entitlements file, an iCloud-enabled provisioning profile, App IDs, and so on. This is now a matter of simply selecting a box. However, the prerequisite is that you need to be registered with Apple’s Developer Program.

Go to the project properties and click the target SuperDB. Under the Capabilities tab, look for iCloud and switch it on (see Figure 8-2).

Figure 8-2.
figure 2figure 2

The project’s Capabilities screen

Xcode will select or prompt you for the relevant team account to use and create the relevant entitlements file, certificates, and so on. Select the appropriate services that you need to enable for this application. There are no settings for the key-value storage; for iCloud Documents, you need to select the appropriate container. This is created by default as iCloud.$(CFBundleIdentifier). In this case, the bundle identifier is com.ozapps.SuperDB, so the container is identified as iCloud.com.ozapps.SuperDB (see Figure 8-3).

Figure 8-3.
figure 3figure 3

The project with iCloud enabled

Whew. That was easy and not a lot of work just to get iCloud activated for your application as it was in the past.

Updating the Persistent Store

In the SuperDB Xcode project window, open AppDelegate.swift and find the persistentStoreCoordinator method. You need to rewrite it to check and use an iCloud persistent store if possible or fall back to a local persistent store. The beginning of the method remains the same: you check whether you’ve already created an instance of your persistent store coordinator; if not, you instantiate one.

var persistentStoreCoordinator: NSPersistentStoreCoordinator? {

    if _persistentStoreCoordinator != nil {

return _persistentStoreCoordinator

    }

    _persistentStoreCoordinator = NSPersistentStoreCoordinator(

                                         managedObjectModel: self.managedObjectModel)

You dispatch the following code to a background queue so as not to block the main thread. The following code is similar to the example provided in the “Core Data with iCloud” section earlier. Review that section for a detailed explanation.

var psc = _persistentStoreCoordinator

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

    var newStore: NSPersistentStore? = nil

    var error: NSError? = nil

    let dataFile = "SuperDB.sqlite"

    let dataDir = "Data.nosync"

    let logsDir = "Logs"

    var fileManager = NSFileManager.defaultManager()

    var ubiquityToken = fileManager.ubiquityIdentityToken

    var ubiquityURL: NSURL? = fileManager.URLForUbiquityContainerIdentifier(nil)

    if (ubiquityToken != nil && ubiquityURL != nil) {

        var dataDirPath = ubiquityURL?.path?.stringByAppendingPathComponent(dataDir)

        if fileManager.fileExistsAtPath(dataDirPath!) == false {

            var fileSystemError: NSError? = nil

            fileManager.createDirectoryAtPath(dataDirPath!,

                              withIntermediateDirectories: true,

                                               attributes: nil,

                                                    error: &fileSystemError)

            if fileSystemError != nil {

                println("Error creating database directory \(fileSystemError)")

            }

        }

        var thePath = ubiquityURL?.path?.stringByAppendingPathComponent(logsDir)

        var logsURL = NSURL(fileURLWithPath: thePath!)

        var options = NSMutableDictionary()

            options[NSMigratePersistentStoresAutomaticallyOption] = true

            options[NSInferMappingModelAutomaticallyOption] = true

            options[NSPersistentStoreUbiquitousContentNameKey] = ubiquityURL?.lastPathComponent!

            options[NSPersistentStoreUbiquitousContentURLKey] = logsURL!

        psc.lock()

        thePath = dataDirPath?.stringByAppendingPathComponent(dataFile)

        var dataFileURL = NSURL.fileURLWithPath(thePath!)

        newStore = psc.addPersistentStoreWithType(NSSQLiteStoreType,

                                    configuration: nil,

                                              URL: dataFileURL,

                                          options: options,

                                            error: &error)

        psc.unlock()

If for some reason you don’t have access to iCloud, you can fall back to using the local persistent store coordinator.

} else {

    var storeURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(dataFile)

    var options = [NSMigratePersistentStoresAutomaticallyOption : true,

                   NSInferMappingModelAutomaticallyOption: true]

    psc.lock()

    newStore = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil,

                                             URL: storeURL, options: options, error: &error)

    psc.unlock()

}

You need to check if you actually have a new persistent store coordinator.

if newStore == nil {

    println("Unresolved error \(error)")

    abort()

}

Once that’s complete, you send a notification on the main thread that you’ve loaded the persistent store coordinator. You use this notification to update the UI, if necessary.

        dispatch_async(dispatch_get_main_queue(), {

            NSNotificationCenter.defaultCenter().postNotificationName("DataChanged",

                object: self, userInfo: nil)

        })

    })

    return _persistentStoreCoordinator

}()

Updating the Managed Object Context

You need to register to receive notifications when the data in the ubiquity container changes. You do that in the lazy initialization of managedObjectContext.

lazy var managedObjectContext: NSManagedObjectContext? = {

    let coordinator = self.persistentStoreCoordinator

if coordinator == nil {

        return nil

    }

    var managedObjectContext = NSManagedObjectContext()

    managedObjectContext.persistentStoreCoordinator = coordinator

    NSNotificationCenter.defaultCenter().addObserver(self,

                       selector: "mergeChangesFromUbiquitousContent:",

                           name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,

                         object: coordinator)

    return managedObjectContext

}()

You’ve told the Notification Center to invoke the AppDelegate method mergeChangesFromUbiquitousContent:, so you need to implement that method.

Then add the implementation to the bottom of AppDelegate.swift, just before the last curly brace.

//MARK: - Handle Changes from iCloud to Ubiquitous Container

func mergeChangesFromUbiquitousContent(notification: NSNotification) {

    var moc = self.managedObjectContext

    moc?.performBlock({

        moc?.mergeChangesFromContextDidSaveNotification(notification)

        var refreshNotification = NSNotification(name: "DataChanged", object: self, userInfo: notification.userInfo)

    })

}

This method first merges the changes into your managed object context. Then it sends a DataChanged notification. You used that notification earlier when you created the persistent store coordinator. It’s intended to notify you when the UI should be updated. Let’s do that.

Updating the UI on DataChanged

Open HeroListController.swift in the Xcode Editor and find the viewDidLoad method. Just before the end of the method, register for the DataChanged notification.

NSNotificationCenter.defaultCenter().addObserver(self,

                              selector: "updateReceived:",

                                  name: "DataChanged",

                                object: nil)

While you’re at it, be a good iOS programmer and unregister in the didReceiveMemoryWarning method.

NSNotificationCenter.defaultCenter().removeObserver(self)

When the DataChanged notification is received, the updateReceived: method will be invoked. So, you need to implement it again before the last curly brace.

func updateReceived(notification: NSNotification) {

    var error: NSError? = nil

    if !self.fetchedResultsController.performFetch(&error){

        println("Error performing fetch: \(error?.localizedDescription)")

    }

    self.heroTableView.reloadData()

}

Essentially, it just refreshes the data and table view.

Testing the Data Store

Don’t believe anyone If they tell you that you can’t use iCloud on the simulator. Since iOS7, Apple has made things slightly better, so now you can test your iCloud code on the simulator. However, it is best if you to run it on your device. Since you’re starting with a new persistent store, there should be no entries. Add a new hero, edit the details, and save. Now quit the application (and/or stop it in Xcode). Tap and hold the SuperDB app icon from the Launchpad until it begins to shake. Delete the app. You should receive an alert dialog to tell you that the local data will be lost, but the iCloud data will be kept. Tap Delete.

Now run the app again. Wait a few moments, and the Hero list should update to include the hero you added earlier. Even though you deleted the app (and its local data), iCloud was able to synchronize and restore the persistent store.

Note

To use iCloud on the simulator, you have to set up your iCloud account on the simulator by logging in.

Keep Your Feet on the Ground

While developing an application for iCloud, there may be times when you want to view or even delete the data in iCloud. There are a few ways you can view and/or manage the data your application is putting in iCloud.

  • Via Mac: Open System preferences, and choose iCloud. Click the Manage button on the lower right.

  • Via iOS: Use the Settings app, and navigate to iCloud ➤ Storage & Backup ➤ Manage Storage.

  • Via the Web (view only): Navigate to http://developer.icloud.com/ and log in. Click the Documents icon.

  • Via Xcode (starts the Web view): If you have set up iCloud under Capabilities in your app, Xcode has a CloudKit Dashboard button that essentially takes you to the Web view, as outlined earlier.

These are just the basics of building an iCloud-enabled application for iOS. For any application, there are many things to keep in mind, but here are some key things to remember:

  • How will your app behave if iCloud is not available?

  • If you allow “offline” use, how will your application synchronize with iCloud?

  • How will your application handle conflicts? This will be highly dependent on your data model.

  • Try to design your data/document model to minimize the data transfer between your device and iCloud.

Ideally, you’ve gotten a good taste of what it means to enable iCloud in your app. Let’s head back to Earth and have some fun building a game.