- Setting Up an iCloud-Compatible Project
- How iCloud Works
- iCloud Container Folders
- Recipe: Trying Out iCloud
- Working with UIDocument
- Recipe: Subclassing UIDocument
- Metadata Queries and the Cloud
- Handy Routines
- Recipe: Accessing the Ubiquitous Key-Value Store
- Recipe: UIManagedDocument and Core Data
- Summary
iCloud Container Folders
Developing and using iCloud is primarily a matter of writing to, monitoring, and reading from the right folder at the right time. Under iOS 5 and later, NSFileManager creates authenticated on-demand URL access to that shared storage, which you can query at will. Your application must be properly entitled for iCloud access to happen. Consider the following call; you would substitute an actual container identifier you added to your entitlement:
NSString *container = [CloudHelper containerize:com.sadun.HelloWorld"]; NSURL *destinationURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier: container];
This call either returns a URL, in the case of proper entitlements, developer prefix, and container identifier or nil. The container the URL points to represents the base of where your application is allowed to store data. This is part of your application’s ubiquitous data scope.
Your data should actually live one step down from this container, in a Documents folder, much as it does in your Sandbox. This is part of your application’s ubiquitous documents scope. Adding a documents folder allows your user to list and delete files individually from their Settings application. iCloud groups any items outside Documents into a single unit; these files must be deleted all at once. By creating a shared document home, you offer your user more nuanced access to his or her data.
You will encounter the terms ubiquitous data scope and ubiquitous documents scope throughout this chapter. Your application’s ubiquitous data scope refers to all iCloud container content that lies outside Documents folders. The documents scope refers to iCloud content within Documents folders.
Building a Shared Document Home
After entitling your application, you’ll be able to establish a home in which your app’s iCloud documents will live. This involves creating a Documents subfolder in your container. The following method requests a URL that points to the container storage. If it does not find a subfolder there, it builds a new Documents folder. By adding it to the container, it mirrors the way your documents are stored in the application sandbox.
As you can see, this involves nothing more than using standard NSFileManager calls. The method checks to see if the folder exists. If it does not, it creates a new subfolder in the container.
// Return the iCloud Data URL + (NSURL *) ubiquityDataURLForContainer: (NSString *) container { return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container]; } // Return the iCloud Documents URL + (NSURL *) ubiquityDocumentsURLForContainer: (NSString *) container { return [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:@"Documents"]; } // Prepare the Documents folder + (BOOL) setupUbiquityDocumentsFolderForContainer: (NSString *) container { NSError *error; NSURL *targetURL = [self ubiquityDocumentsURLForContainer:container]; // Create the ubiquity documents folder if needed if (![[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:targetURL.path withIntermediateDirectories:YES attributes:nil error:&error]) { NSLog(@"Error: %@", error.localizedFailureReason); return NO; } } return YES; }
Writing To and Reading From Ubiquitous Storage
“Ubiquitous” items refer to those files that live in iCloud. Once you have a URL that points to your ubiquitous documents folder and not just the primary container, you may write to it and read from it just as you would in your sandbox. Be cautioned. This is generally a bad idea unless your files are entirely atomic, so you will not encounter any conflicts. Any file you create in that folder automatically updates its contents to iCloud.
Latency depends on network connectivity and device settings. Usually, small files update within a few seconds. You can monitor the connectivity by examining a device’s console logs. Status change notifications indicate what percentage of the document has uploaded, ending with the item becoming current.
Jul 9 16:16:05 unknown librariand[50] <Notice>: STATUS CHANGE: url = /private/var/mobile/Library/Mobile Docu- ments/XYZZYPLUGH~com~sadun~CloudLearning/Documents/Output2.jpg status = 0xd02 (Has- Revisions New Current InCloud) transferred = 0 Jul 9 16:16:05 unknown librariand[50] <Notice>: STATUS CHANGE: url = /private/var/mobile/Library/Mobile Docu- ments/XYZZYPLUGH~com~sadun~CloudLearning/Documents/Output2.jpg status = 0x40c02 (HasRevisions Current InCloud Uploading) transferred = 50 Jul 9 16:16:05 unknown librariand[50] <Notice>: <LBFileProvider: 0xe82c900> itemIsNowCurrent: - url=/private/var/mobile/Library/Mobile Docu- ments/XYZZYPLUGH~com~sadun~CloudLearning/Documents/Output2.jpg
Using the Organizer’s device console logs allows you to monitor iCloud updates throughout your application runs. As there are no official notifications that you can subscribe to, I recommend leaving the console open as you develop and debug your iCloud apps. This allows you to better see what is going on behind the scenes with the iCloud daemon, providing live feedback of those changes.
Editing iCloud Storage
Determine which application files exist in your iCloud storage by visiting Settings > iCloud > Storage & Backup > Manage Storage > Documents and Data. There, listed (currently) under Unknown, you’ll find any application-generated items. Figure 18-2 shows a single third-party text file created by my test application. Hopefully, Apple will update this in future releases to reflect the document source rather than “Unknown.”
Figure 18-2 Application-generated iCloud documents appear in the Documents & Data section in Settings.
You can delete cloud files here by swiping-to-delete or tapping Edit. Removing a file here removes it from the cloud folder on your device and from the iCloud central storage. This is especially important for testing, because it allows you to reset across all devices.
Designing for iCloud Ubiquity
There are many issues to take into account when building applications for iCloud. As a rule, design your non-Core Data applications around UIDocument and your Core Data applications around UIManagedDocument. These classes were built for iCloud from the ground up and help simplify many of the synchronization issues that arise from using ubiquitous data.
When using iCloud, let your users choose which items to save to iCloud. Documents can live just as easily in your sandbox as they can in the cloud. Never force your users to pay extra money to Apple to gain ubiquitous access for items they don’t want or need.
Be clever when letting users freely move their data into and out of the cloud. Removing data from the cloud can mean two things. The user may want to delete the file entirely, or they may want to move the data from the cloud to the device. Whether deleting or transferring, moving a file from the cloud removes that data from all other devices. Design for both scenarios.
When you need a file to survive its deletion from the cloud on a device that did not initiate its removal, you can take advantage of a user’s Library/Caches folder. Proactively caching copies of the data whenever the device is used allows you to store local versions so users don’t entirely lose access to formerly ubiquitous data. The caches folder isn’t backed up either by iTunes or iCloud, so you don’t place as big a burden on a user’s paid iCloud quota. At the same time, local caching does affect available space for the device, and you should allow the cached copy to be removed when the user no longer wants access to the data.
In an ideal world, Apple would introduce an optional iCloud pending-deletion state in addition to deleting cloud items immediately or transferring them from the cloud to the sandbox. Such an option would allow applications to check items out of the cloud on each registered device; the file would be removed once all registered devices were offered the opportunity to copy the cloud-stored version or when the user forced a full delete request.
You can create a rough approximation of this behavior by adding an “automatically check out copies of this document” feature to your applications. Copy the document from cloud to cache, and then, once the application detects that the document is no longer available from iCloud, move it from cache to sandbox for local use.
As a final note, remember that file names are case-sensitive on iOS, but are not on OS X. Take care when creating iCloud file names on iOS so you do not establish pairs of names that differ only by case. Add simple in-app checks to prevent users from doing so.