harrington icloud presentation
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
JohnnyDoe
JohnDoe
JohnnyDough
JohnnyDough
JohnDoe
JohnDoe
JohnDoe
?JohnDoe
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