Lecture #17: iCloud

Please note, this blog entry is from a previous course. You might want to check out the current one.

Lecture seventeen is named “17. iCloud (November 29, 2011)” and can be found at iTunes. Its slides are available at Stanford.

This lecture together with the following one addresses the iCloud and how to use it for sharing documents among user’s devices. Using the iCloud is like using an URL of a shared directory with restrictions deriving from latency and shared access. It is setup in Xcode by enabling Entitlements.

URLs for the iCloud are derived via the file manager:

[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];


wherein the actual documents are stored in the @”Documents” folder. The file manger can be used for the iCloud, but should be used outside the main queue due to the latency of the network. Enumeration should not be used to access directory contains, as they would need an always-updating query. The file coordination among devices should happen among NSFilePresenters using the NSFileCoordinator class.

The NSFilePresenter is an abstraction of the actual file or folder. Its API is not topic of this class, but UIManagedDocument which is an NSFilePresenter. UIDocument automatically coordinates document changes and can be used override the following methods:

- (id)contentsForType:(NSString *)type 
                error:(NSError **)error;
- (BOOL)loadFromContents:(id)contents 
                  ofType:(NSString *)type 
                   error:(NSError **)error; 

UIManagedDocuments which are used in Core Data are fully iCloud capable. To stay in sync notifications are used. As mentioned above managed documents should reside on the cloud in

iCloudDocumentsURL = [iCloudURL URLByAppendingPathComponent:@“Documents”];

To improve the performance it is essential that only changes are communicated between the device and the cloud. Thus the persistentStoreOptions dictionary must provide an NSPersistentStoreUbiquitousContentNameKey for the name of the document and an NSPersistentStoreUbiquitousContentURLKey for its change logs, where the second one should not be stored in the documents directory, but all documents can share the same key, e.g.:

[iCloudURL URLByAppendingPathComponent:@“CoreData”]

The name key should be provided by the document’s metadata, e.g.:

NSURL *metadataURL = [docURL URLByAppendingPathComponent:@“DocumentMetadata.plist”];

To enumerate over what’s in the cloud, a query must be created, started, and checked for changes using notifications, e.g.:

NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; 
query.searchScopes = [NSArray arrayWithObjects:scope1, scope2, nil];
// NSMetadataQueryUbiquitousDocumentsScope = all files inside Documents
// NSMetadataQueryUbiquitousDataScope = all files outside Documents
query.predicate =
   [NSPredicate predicateWithFormat:@"%K like '*'", NSMetadataItemFSNameKey];
[query startQuery];
[query enableUpdates];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 
[center addObserver:self
           selector:@selector(processQueryResults:) 
               name:NSMetadataQueryDidFinishGatheringNotification // first results
             object:query];
[center addObserver:self
           selector:@selector(processQueryResults:) 
               name:NSMetadataQueryDidUpdateNotification // subsequent updates
             object:query];
....
- (void)processQueryResults:(NSNotification *)notification
    [query disableUpdates];
    int resultCount = [query resultCount];
    for (int i = 0; i < resultCount; i++) {
        NSMetadataItem *item = [query resultAtIndex:i];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
        // do something
    }
    [query enableUpdates];
}
....
[[NSNotificationCenter defaultCenter] removeObserver:self];
[query disableUpdates];
[query stopQuery];

When an iCloud URL is accessed using the file manager its changes should be coordinated outside the main thread, e.g.:

NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; 
NSError *coordinationError;
[coordinator coordinateReadingItemAtURL:(NSURL *)url
                                options:(NSFileCoordinatorReadingOptions)options
                                  error:(NSError **)error
                             byAccessor:^(NSURL *urlToUse) { 
    // do your NSFileManager stuff here using urlToUse
}];
if (coordinationError) { } // handle error

Using iCloud it is important to watch the documentState especially for EditingDisabled and InConflict but also for SavingError and perhaps retry a saving process.

When conflicts appear they must be handled by deciding which document version should be used or how they should be merged, e.g. by setting a mergePolicy.

When EditingDisabled is set the user should not be able to modify the document.

Moving a file to or from the iCloud should be handled outside the main thread, e.g.:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURL *localURL = ...;
    NSString *name = [localURL lastPathComponent];
    NSURL *iCloudURL = [iCloudDocumentsURL URLByAppendingPathComponent:name]; 
    NSError *error = nil;
    [[[NSFileManager alloc] init] setUbiquitous:YES
                                      itemAtURL:localURL
                                 destinationURL:iCloudURL
                                          error:&error];
});

It is also possible to use iCloud to share files between users using temporary read-only URLs, e.g.:

NSURL *urlToShare = ...; 
NSDate *date;
NSError *error;
NSURL *sharedURL =
    [[NSFileManager defaultManager] URLForPublishingUbiquitousItemAtURL:urlToShare
                                                         expirationDate:&date
                                                                  error:&error];

NSUbiquitousKeyValueStore is an iCloud equivalent to NSUserDefaults but very limited in size – 64 kbit overall and per value.

If something in the cloud changes notifications have to be used to get informed, e.g.:

NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 
[center addObserver:self
           selector:@selector(ubiquitousKeyValueStoreChanged:)
               name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
             object:[NSUbiquitousKeyValueStore defaultStore]];

- (void)ubiquitousKeyValueStoreChanged:(NSNotification *)notification
{
    // notification.userInfo contains ...
    // NSUbiquitousKeyValueStoreChangeReasonKey (Server, InitialSync, QuotaViolation)      
    // NSUbiquitousKeyValueStoreChangedKeysKey (NSArray of NSStrings of keys that changed)
}

The demo of this lecture adds iCloud functionality to the Photomania application of lecture #14 and demonstrates how to use blocks for sorting. Its code is available on github.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

Leave a Reply

Your email address will not be published. Required fields are marked *