harrington icloud presentation

108
iCloud Build your Data Castle in the Air Tom Harrington @atomicbird

Upload: sergey-zenchenko

Post on 20-Feb-2015

122 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Harrington iCloud Presentation

iCloudBuild your Data Castle in the Air

Tom Harrington@atomicbird

Page 2: Harrington iCloud Presentation
Page 3: Harrington iCloud Presentation
Page 4: Harrington iCloud Presentation
Page 5: Harrington iCloud Presentation
Page 6: Harrington iCloud Presentation

What’s Ahead

• Concepts

• APIs

• Pitfalls

Page 7: Harrington iCloud Presentation

What is iCloud?

Page 8: Harrington iCloud Presentation

What is iCloud?

• Sync service

• Transfers user data between devices

• Runs in background

• Per-app sandboxing (as usual)

Page 9: Harrington iCloud Presentation

What isn’t iCloud?

• Dropbox

• File browser

• iCloud is about Your Stuff

Page 10: Harrington iCloud Presentation

Infrastructure

• When using iCloud you

• Don’t need your own sync servers

• Don’t need your own network protocol

• Don’t need to pay for storage/bandwidth

Page 11: Harrington iCloud Presentation

Availability

• iOS 5+

• Free accounts get 5GB of storage

• This includes iCloud device backups

Page 12: Harrington iCloud Presentation
Page 13: Harrington iCloud Presentation

Conflicts

• iCloud helps with conflicts

• But does not solve the problem

Page 14: Harrington iCloud Presentation

iCloud Concepts and Terms

Page 15: Harrington iCloud Presentation

Ubiquitous

Page 16: Harrington iCloud Presentation

Background Syncing

• Ubiquity daemon, ubd

• You save changes, ubd sends them to the cloud

• ubd downloads changes from iCloud

• Which means....

Page 17: Harrington iCloud Presentation

Your data can change without warning

Page 18: Harrington iCloud Presentation

iCloud Containers

• Location for your app’s data in iCloud

• Every iCloud enabled app has at least one

• Containers correspond to a directory

Page 19: Harrington iCloud Presentation

File Coordinator

• Data can be changed by your app or by ubd

• Need a reader/writer lock to coordinate access

• Use NSFileCoordinator when reading or writing

Page 20: Harrington iCloud Presentation

File Presenter

• Companion to the file coordinator

• Adopt NSFilePresenter to be notified of changes

• When the file coordinator makes a change, it calls the file presenter.

Page 21: Harrington iCloud Presentation

APIs

• Key-value store

• Core Data

• Document

Page 22: Harrington iCloud Presentation

Tip

• ~/Library/Mobile Documents/

Page 23: Harrington iCloud Presentation

Enabling iCloud

Page 24: Harrington iCloud Presentation

Yo dawg I heard you like provisioning...

Page 25: Harrington iCloud Presentation
Page 26: Harrington iCloud Presentation
Page 27: Harrington iCloud Presentation
Page 28: Harrington iCloud Presentation
Page 29: Harrington iCloud Presentation
Page 30: Harrington iCloud Presentation

iCloud APIs

Page 31: Harrington iCloud Presentation

Is iCloud Available?

Page 32: Harrington iCloud Presentation

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

File URL pointing to your iCloud container directory

Page 33: Harrington iCloud Presentation

Key-Value Store

Page 34: Harrington iCloud Presentation

NSUbiquitousKeyValueStore

• API is very similar to NSUserDefaults

• Global dictionary with convenience accessors

Page 35: Harrington iCloud Presentation

- (NSString *)stringForKey:(NSString *)aKey;- (NSArray *)arrayForKey:(NSString *)aKey;- (NSDictionary *)dictionaryForKey:(NSString *)aKey;- (NSData *)dataForKey:(NSString *)aKey;- (long long)longLongForKey:(NSString *)aKey;- (double)doubleForKey:(NSString *)aKey;- (BOOL)boolForKey:(NSString *)aKey;

- (void)setString:(NSString *)aString forKey:(NSString *)aKey;- (void)setData:(NSData *)aData forKey:(NSString *)aKey;- (void)setArray:(NSArray *)anArray forKey:(NSString *)aKey;- (void)setDictionary:(NSDictionary *)aDictionary forKey:(NSString *)aKey;- (void)setLongLong:(long long)value forKey:(NSString *)aKey;- (void)setDouble:(double)value forKey:(NSString *)aKey;- (void)setBool:(BOOL)value forKey:(NSString *)aKey;

Page 36: Harrington iCloud Presentation

Key Value Store API

• No equivalent to -registerDefaults:

• The -synchronize method writes to disk only

• Can use –synchronize to check iCloud availability

- (BOOL)synchronize;

Page 37: Harrington iCloud Presentation

Key Value Store

• Limited to 64kB per app

• Recommended for app state

• Not recommended for user defaults

Page 38: Harrington iCloud Presentation
Page 39: Harrington iCloud Presentation

KV Store: Conflicts

• Last setting into iCloud wins.

• No conflicts, so no conflict resolution

• NSUbiquitousKeyValueStoreDidChangeExternallyNotification

Page 40: Harrington iCloud Presentation

Core Data, meet iCloud

Page 41: Harrington iCloud Presentation

iCloud Core Data

• Data store is still in your app sandbox

• New options when setting up the stack

• Adding iCloud to existing apps is surprisingly easy

Page 42: Harrington iCloud Presentation

Internals, briefly

• Data store files do not get synced

• Transaction logs written to the iCloud container

• Transactions are synced and replayed

• Only changed records are synced

Page 43: Harrington iCloud Presentation

Do not put SQLite files in iCloud

Page 44: Harrington iCloud Presentation

Demo Code: Recipes

Page 45: Harrington iCloud Presentation

- (void)dealloc { [managedObjectContext__ release]; [managedObjectContext__ release]; [managedObjectContext__ release]; [recipeListController release]; [tabBarController release]; [window release]; [super dealloc];}

Page 46: Harrington iCloud Presentation

Persistent Store Setup

• NSPersistentStoreUbiquitousContentNameKey

• Arbitrary string, iCloud name for persistent store

• NSPersistentStoreUbiquitousContentURLKey

• Sub-directory in the iCloud container

• Optional (but a good idea)

Page 47: Harrington iCloud Presentation

File coordination is automatic

Page 48: Harrington iCloud Presentation

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator__ != nil) { return persistentStoreCoordinator__; }

persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:persistentStoreCoordinator__];

NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // More to come }); return persistentStoreCoordinator__;}

Page 49: Harrington iCloud Presentation

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator__ != nil) { return persistentStoreCoordinator__; }

persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:persistentStoreCoordinator__];

NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // More to come }); return persistentStoreCoordinator__;}

Page 50: Harrington iCloud Presentation

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator__ != nil) { return persistentStoreCoordinator__; }

persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:persistentStoreCoordinator__];

NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // More to come }); return persistentStoreCoordinator__;}

Page 51: Harrington iCloud Presentation

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"recipes_v3"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:! ! ! ! ! ! ! ! @"com.apple.coredata.examples.recipes.3", NSPersistentStoreUbiquitousContentNameKey,! ! ! ! ! ! ! ! cloudURL, NSPersistentStoreUbiquitousContentURLKey,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,! ! ! ! ! ! ! ! nil];

NSError *error = nil; [persistentStoreCoordinator__ lock]; if (![persistentStoreCoordinator__ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) { // Handle error... } [persistentStoreCoordinator__ unlock];

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"asynchronously added persistent store!"); [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); });

Page 52: Harrington iCloud Presentation

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"recipes_v3"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:! ! ! ! ! ! ! ! @"com.apple.coredata.examples.recipes.3", NSPersistentStoreUbiquitousContentNameKey,! ! ! ! ! ! ! ! cloudURL, NSPersistentStoreUbiquitousContentURLKey,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,! ! ! ! ! ! ! ! nil];

NSError *error = nil; [persistentStoreCoordinator__ lock]; if (![persistentStoreCoordinator__ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) { // Handle error... } [persistentStoreCoordinator__ unlock];

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"asynchronously added persistent store!"); [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); });

Page 53: Harrington iCloud Presentation

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"recipes_v3"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:! ! ! ! ! ! ! ! @"com.apple.coredata.examples.recipes.3", NSPersistentStoreUbiquitousContentNameKey,! ! ! ! ! ! ! ! cloudURL, NSPersistentStoreUbiquitousContentURLKey,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,! ! ! ! ! ! ! ! nil];

NSError *error = nil; [persistentStoreCoordinator__ lock]; if (![persistentStoreCoordinator__ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) { // Handle error... } [persistentStoreCoordinator__ unlock];

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"asynchronously added persistent store!"); [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); });

Page 54: Harrington iCloud Presentation

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"recipes_v3"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:! ! ! ! ! ! ! ! @"com.apple.coredata.examples.recipes.3", NSPersistentStoreUbiquitousContentNameKey,! ! ! ! ! ! ! ! cloudURL, NSPersistentStoreUbiquitousContentURLKey,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,! ! ! ! ! ! ! ! [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,! ! ! ! ! ! ! ! nil];

NSError *error = nil; [persistentStoreCoordinator__ lock]; if (![persistentStoreCoordinator__ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) { // Handle error... } [persistentStoreCoordinator__ unlock];

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"asynchronously added persistent store!"); [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); });

Page 55: Harrington iCloud Presentation

Getting Incoming Changes

Page 56: Harrington iCloud Presentation

When changes download...

• ubd downloads transaction logs and replays them

• Notification posted when save is complete

• This notification will post more than once

Page 57: Harrington iCloud Presentation

- (void)mergeChangesFrom_iCloud:(NSNotification *)notification { NSManagedObjectContext* moc = [self managedObjectContext];

[moc mergeChangesFromContextDidSaveNotification:notification];

[[NSNotificationCenter defaultCenter] postNotificationName:@"RefreshAllViews" object:self userInfo:[note userInfo]];}

Page 58: Harrington iCloud Presentation

Conflict Handling

Page 59: Harrington iCloud Presentation

JohnDoe

[email protected]

JohnnyDoe

[email protected]

JohnDoe

[email protected]

JohnnyDough

[email protected]

JohnnyDough

[email protected]

Page 61: Harrington iCloud Presentation

Core Data Migration

• Syncing only happens if the data model is the same

• Different versions of an app may not sync

• Syncing resumes after upgrade

• Automatic lightweight migration works

Page 62: Harrington iCloud Presentation

Ubiquitous Documents

Page 63: Harrington iCloud Presentation

UIDocument

Page 64: Harrington iCloud Presentation

UIDocument

• Asynchronous block-based reading and writing

• Auto-saving

• Flat file and file packages

Page 65: Harrington iCloud Presentation

UIDocument with iCloud

• Acts as a file coordinator and presenter

• Detects conflicts

• Notifications

• API to help resolve

Page 66: Harrington iCloud Presentation

UIManagedDocument

• Concrete subclass of UIDocument

• Stores document data in Core Data

• Each instance has its own Core Data stack

Page 67: Harrington iCloud Presentation

UIDocument Concepts

Page 68: Harrington iCloud Presentation

Outside the (sand)box

• iCloud documents are not located in your app sandbox

• Located in your iCloud container

• Create the document in the sandbox

• Move it to iCloud

Page 69: Harrington iCloud Presentation

Finding documents

• You can’t just scan the iCloud container for documents

• Use NSMetadataQuery

• This is what powers Spotlight on Macs

Page 70: Harrington iCloud Presentation

Why search?

• Documents might not exist locally (yet)

• Metadata may be all there is

• Outgoing data is pushed aggressively

• Incoming data is downloaded on demand

Page 71: Harrington iCloud Presentation

Document StateUIDocumentStateNormal

UIDocumentStateClosed

UIDocumentStateInConflict

UIDocumentStateSavingError

UIDocumentStateEditingDisabled

UIDocumentStateChangedNotification

Page 72: Harrington iCloud Presentation

Demo Code: CloudNotes

Page 73: Harrington iCloud Presentation
Page 74: Harrington iCloud Presentation

@interface NoteDocument : UIDocument

@property (strong, readwrite) NSString *documentText;@end

Subclassing UIDocument

Page 75: Harrington iCloud Presentation

Subclassing UIDocument

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError;

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError;

Page 76: Harrington iCloud Presentation

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ NSString *text = nil; if ([contents length] > 0) { text = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding]; } else { text = @""; } [self setDocumentText:text]; return YES;}

Page 77: Harrington iCloud Presentation

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ if ([[self documentText] length] == 0) { [self setDocumentText:@"New note"]; } return [NSData dataWithBytes:[[self documentText] UTF8String] length:[[self documentText] length]];}

Page 78: Harrington iCloud Presentation

Creating a NoteDocument

Page 79: Harrington iCloud Presentation

- (NSURL*)ubiquitousDocumentsDirectoryURL{ NSURL *ubiquitousContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

NSURL *ubiquitousDocumentsURL = [ubiquitousContainerURL URLByAppendingPathComponent:@"Documents"];

if (ubiquitousDocumentsURL != nil) { if (![[NSFileManager defaultManager] fileExistsAtPath:[ubiquitousDocumentsURL path]]) { NSError *createDirectoryError = nil; BOOL created = [[NSFileManager defaultManager] createDirectoryAtURL:ubiquitousDocumentsURL withIntermediateDirectories:YES attributes:0 error:&createDirectoryError]; if (!created) { NSLog(@"Error creating directory at %@: %@", ubiquitousDocumentsURL, createDirectoryError); } } } else { NSLog(@"Error getting ubiquitous container URL"); } return ubiquitousDocumentsURL;}

Page 80: Harrington iCloud Presentation

- (NSURL*)ubiquitousDocumentsDirectoryURL{ NSURL *ubiquitousContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

NSURL *ubiquitousDocumentsURL = [ubiquitousContainerURL URLByAppendingPathComponent:@"Documents"];

if (ubiquitousDocumentsURL != nil) { if (![[NSFileManager defaultManager] fileExistsAtPath:[ubiquitousDocumentsURL path]]) { NSError *createDirectoryError = nil; BOOL created = [[NSFileManager defaultManager] createDirectoryAtURL:ubiquitousDocumentsURL withIntermediateDirectories:YES attributes:0 error:&createDirectoryError]; if (!created) { NSLog(@"Error creating directory at %@: %@", ubiquitousDocumentsURL, createDirectoryError); } } } else { NSLog(@"Error getting ubiquitous container URL"); } return ubiquitousDocumentsURL;}

Page 81: Harrington iCloud Presentation

- (void)createFileNamed:(NSString *)filename{ NSURL *localFileURL = [[self localDocumentsDirectoryURL] URLByAppendingPathComponent:filename]; NoteDocument *newDocument = [[NoteDocument alloc] initWithFileURL:localFileURL];

[newDocument saveToURL:localFileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *destinationURL = [[self ubiquitousDocumentsDirectoryURL] URLByAppendingPathComponent:filename];

NSError *moveToCloudError = nil; BOOL success = [[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:[newDocument fileURL] destinationURL:destinationURL error:&moveToCloudError];

if (!success) { NSLog(@"Error moving to iCloud: %@", moveToCloudError); } }); } }];}

Page 82: Harrington iCloud Presentation

- (void)createFileNamed:(NSString *)filename{ NSURL *localFileURL = [[self localDocumentsDirectoryURL] URLByAppendingPathComponent:filename]; NoteDocument *newDocument = [[NoteDocument alloc] initWithFileURL:localFileURL];

[newDocument saveToURL:localFileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *destinationURL = [[self ubiquitousDocumentsDirectoryURL] URLByAppendingPathComponent:filename];

NSError *moveToCloudError = nil; BOOL success = [[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:[newDocument fileURL] destinationURL:destinationURL error:&moveToCloudError];

if (!success) { NSLog(@"Error moving to iCloud: %@", moveToCloudError); } }); } }];}

Page 83: Harrington iCloud Presentation

- (void)createFileNamed:(NSString *)filename{ NSURL *localFileURL = [[self localDocumentsDirectoryURL] URLByAppendingPathComponent:filename]; NoteDocument *newDocument = [[NoteDocument alloc] initWithFileURL:localFileURL];

[newDocument saveToURL:localFileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *destinationURL = [[self ubiquitousDocumentsDirectoryURL] URLByAppendingPathComponent:filename];

NSError *moveToCloudError = nil; BOOL success = [[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:[newDocument fileURL] destinationURL:destinationURL error:&moveToCloudError];

if (!success) { NSLog(@"Error moving to iCloud: %@", moveToCloudError); } }); } }];}

Page 84: Harrington iCloud Presentation

- (void)createFileNamed:(NSString *)filename{ NSURL *localFileURL = [[self localDocumentsDirectoryURL] URLByAppendingPathComponent:filename]; NoteDocument *newDocument = [[NoteDocument alloc] initWithFileURL:localFileURL];

[newDocument saveToURL:localFileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *destinationURL = [[self ubiquitousDocumentsDirectoryURL] URLByAppendingPathComponent:filename];

NSError *moveToCloudError = nil; BOOL success = [[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:[newDocument fileURL] destinationURL:destinationURL error:&moveToCloudError];

if (!success) { NSLog(@"Error moving to iCloud: %@", moveToCloudError); } }); } }];}

Page 85: Harrington iCloud Presentation

Opening and Closing

[[self currentDocument] openWithCompletionHandler:^(BOOL success) { [[self detailViewController] setDocument:document];}];

if (!([[self currentDocument] documentState] & UIDocumentStateClosed)) { [[self currentDocument] closeWithCompletionHandler:^(BOOL success) { }];}

Page 86: Harrington iCloud Presentation

Finding iCloud documents

Page 87: Harrington iCloud Presentation

NSMetadataQuery

• Configure with an NSPredicate

• Give it a scope to search

• Posts notifications when results are available

Page 88: Harrington iCloud Presentation

NSMetadataQuery

• New search scopes

• NSMetadataQueryUbiquitousDocumentsScope

• NSMetadataQueryUbiquitousDataScope

• Notifies when results have been found

• Just let it keep running

Page 89: Harrington iCloud Presentation

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];

[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];

[query setPredicate:[NSPredicate predicateWithFormat:@"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidFinishGatheringNotification object:query];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidUpdateNotification object:query];

[query startQuery];

Page 90: Harrington iCloud Presentation

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];

[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];

[query setPredicate:[NSPredicate predicateWithFormat:@"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidFinishGatheringNotification object:query];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidUpdateNotification object:query];

[query startQuery];

Page 91: Harrington iCloud Presentation

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];

[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];

[query setPredicate:[NSPredicate predicateWithFormat:@"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidFinishGatheringNotification object:query];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileListReceived) name:NSMetadataQueryDidUpdateNotification object:query];

[query startQuery];

Page 92: Harrington iCloud Presentation

But is it available?

NSNumber *isIniCloud = nil;if ([fileURL getResourceValue:&isIniCloud forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) {}

• Look it up on the document’s file URL

Page 93: Harrington iCloud Presentation

NSURLIsUbiquitousItemKey

NSURLUbiquitousItemIsDownloadedKeyNSURLUbiquitousItemIsDownloadingKeyNSURLUbiquitousItemPercentDownloadedKey

NSURLUbiquitousItemIsUploadedKeyNSURLUbiquitousItemIsUploadingKeyNSURLUbiquitousItemPercentUploadedKey

NSURLUbiquitousItemHasUnresolvedConflictsKey

Page 94: Harrington iCloud Presentation

Forcing Download

NSFileManager* fm = [NSFileManager defaultManager];[fm startDownloadingUbiquitousItemAtURL:file error:nil];

Page 95: Harrington iCloud Presentation

Autosave

• Mark the document as dirty

• Either directly or by registering undo actions

• Periodically it auto-saves its contents

Page 96: Harrington iCloud Presentation

- (void)textViewDidChange:(UITextView *)textView{! [[self document] setDocumentText:[textView text]];! // Trigger auto-save! [[self document] updateChangeCount:UIDocumentChangeDone];}

Page 97: Harrington iCloud Presentation

Document Conflicts

• UIDocumentStateInConflict

• Can retrieve conflicted versions with NSFileVersion

• Must resolve conflicts, because conflict versions stick around until you do

Page 98: Harrington iCloud Presentation

NSFileVersion

+ (NSArray *)unresolvedConflictVersionsOfFileAtURL:(NSURL *)fileURL;

+ (NSFileVersion *)currentVersionOfFileAtURL:(NSURL *)fileURL;

+ (NSFileVersion *)otherVersionsOfFileAtURL:(NSURL *)fileURL;

- (NSURL *)URL;

Page 99: Harrington iCloud Presentation

if (documentState & UIDocumentStateInConflict) { NSURL *documentURL = [[self document] fileURL];

NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:documentURL];

for (NSFileVersion *fileVersion in conflictVersions) { [fileVersion setResolved:YES]; } [NSFileVersion removeOtherVersionsOfItemAtURL:documentURL error:nil];}

Page 100: Harrington iCloud Presentation

Document State Changes

Page 101: Harrington iCloud Presentation

UIDocumentStateEditingDisabled

UIDocumentStateEditingDisabled & UIDocumentStateInConflict

UIDocumentStateEditingDisabled

UIDocumentStateNormal

Conflict States

Non-conflict updatesUIDocumentStateEditingDisabled

UIDocumentStateNormal

Page 102: Harrington iCloud Presentation
Page 103: Harrington iCloud Presentation
Page 104: Harrington iCloud Presentation
Page 105: Harrington iCloud Presentation
Page 106: Harrington iCloud Presentation

No way to check user quota

Page 107: Harrington iCloud Presentation

Conflict Resolution

Page 108: Harrington iCloud Presentation

Go make some happy little iClouds

iCloud Recipeshttp://tinyurl.com/iCloudRecipes

CloudNoteshttps://github.com/atomicbird/CloudNotes