harrington icloud presentation

Post on 20-Feb-2015

123 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

iCloudBuild your Data Castle in the Air

Tom Harrington@atomicbird

What’s Ahead

• Concepts

• APIs

• Pitfalls

What is iCloud?

What is iCloud?

• Sync service

• Transfers user data between devices

• Runs in background

• Per-app sandboxing (as usual)

What isn’t iCloud?

• Dropbox

• File browser

• iCloud is about Your Stuff

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

Availability

• iOS 5+

• Free accounts get 5GB of storage

• This includes iCloud device backups

Conflicts

• iCloud helps with conflicts

• But does not solve the problem

iCloud Concepts and Terms

Ubiquitous

Background Syncing

• Ubiquity daemon, ubd

• You save changes, ubd sends them to the cloud

• ubd downloads changes from iCloud

• Which means....

Your data can change without warning

iCloud Containers

• Location for your app’s data in iCloud

• Every iCloud enabled app has at least one

• Containers correspond to a directory

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

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.

APIs

• Key-value store

• Core Data

• Document

Tip

• ~/Library/Mobile Documents/

Enabling iCloud

Yo dawg I heard you like provisioning...

iCloud APIs

Is iCloud Available?

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

File URL pointing to your iCloud container directory

Key-Value Store

NSUbiquitousKeyValueStore

• API is very similar to NSUserDefaults

• Global dictionary with convenience accessors

- (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;

Key Value Store API

• No equivalent to -registerDefaults:

• The -synchronize method writes to disk only

• Can use –synchronize to check iCloud availability

- (BOOL)synchronize;

Key Value Store

• Limited to 64kB per app

• Recommended for app state

• Not recommended for user defaults

KV Store: Conflicts

• Last setting into iCloud wins.

• No conflicts, so no conflict resolution

• NSUbiquitousKeyValueStoreDidChangeExternallyNotification

Core Data, meet iCloud

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

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

Do not put SQLite files in iCloud

Demo Code: Recipes

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

Persistent Store Setup

• NSPersistentStoreUbiquitousContentNameKey

• Arbitrary string, iCloud name for persistent store

• NSPersistentStoreUbiquitousContentURLKey

• Sub-directory in the iCloud container

• Optional (but a good idea)

File coordination is automatic

- (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__;}

- (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__;}

- (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__;}

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]; }); });

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]; }); });

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]; }); });

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]; }); });

Getting Incoming Changes

When changes download...

• ubd downloads transaction logs and replays them

• Notification posted when save is complete

• This notification will post more than once

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

[moc mergeChangesFromContextDidSaveNotification:notification];

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

Conflict Handling

JohnDoe

john@apple.com

JohnnyDoe

john@apple.com

JohnDoe

johnny@apple.com

JohnnyDough

johnny@apple.com

JohnnyDough

john@apple.com

JohnDoe

john@apple.com

JohnDoe

jd@apple.com

JohnDoe

johnny@apple.com

?JohnDoe

????@apple.com

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

Ubiquitous Documents

UIDocument

UIDocument

• Asynchronous block-based reading and writing

• Auto-saving

• Flat file and file packages

UIDocument with iCloud

• Acts as a file coordinator and presenter

• Detects conflicts

• Notifications

• API to help resolve

UIManagedDocument

• Concrete subclass of UIDocument

• Stores document data in Core Data

• Each instance has its own Core Data stack

UIDocument Concepts

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

Finding documents

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

• Use NSMetadataQuery

• This is what powers Spotlight on Macs

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

Document StateUIDocumentStateNormal

UIDocumentStateClosed

UIDocumentStateInConflict

UIDocumentStateSavingError

UIDocumentStateEditingDisabled

UIDocumentStateChangedNotification

Demo Code: CloudNotes

@interface NoteDocument : UIDocument

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

Subclassing UIDocument

Subclassing UIDocument

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

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

- (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;}

- (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]];}

Creating a NoteDocument

- (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;}

- (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;}

- (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); } }); } }];}

- (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); } }); } }];}

- (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); } }); } }];}

- (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); } }); } }];}

Opening and Closing

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

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

Finding iCloud documents

NSMetadataQuery

• Configure with an NSPredicate

• Give it a scope to search

• Posts notifications when results are available

NSMetadataQuery

• New search scopes

• NSMetadataQueryUbiquitousDocumentsScope

• NSMetadataQueryUbiquitousDataScope

• Notifies when results have been found

• Just let it keep running

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];

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];

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];

But is it available?

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

• Look it up on the document’s file URL

NSURLIsUbiquitousItemKey

NSURLUbiquitousItemIsDownloadedKeyNSURLUbiquitousItemIsDownloadingKeyNSURLUbiquitousItemPercentDownloadedKey

NSURLUbiquitousItemIsUploadedKeyNSURLUbiquitousItemIsUploadingKeyNSURLUbiquitousItemPercentUploadedKey

NSURLUbiquitousItemHasUnresolvedConflictsKey

Forcing Download

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

Autosave

• Mark the document as dirty

• Either directly or by registering undo actions

• Periodically it auto-saves its contents

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

Document Conflicts

• UIDocumentStateInConflict

• Can retrieve conflicted versions with NSFileVersion

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

NSFileVersion

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

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

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

- (NSURL *)URL;

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];}

Document State Changes

UIDocumentStateEditingDisabled

UIDocumentStateEditingDisabled & UIDocumentStateInConflict

UIDocumentStateEditingDisabled

UIDocumentStateNormal

Conflict States

Non-conflict updatesUIDocumentStateEditingDisabled

UIDocumentStateNormal

No way to check user quota

Conflict Resolution

Go make some happy little iClouds

iCloud Recipeshttp://tinyurl.com/iCloudRecipes

CloudNoteshttps://github.com/atomicbird/CloudNotes

top related