Beruflich Dokumente
Kultur Dokumente
Whats 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)
Infrastructure
When using iCloud you Dont need your own sync servers Dont need your own network protocol Dont need to pay for storage/bandwidth
Availability
iOS 5+ Free accounts get 5GB of storage This includes iCloud device backups
Conicts
iCloud helps with conicts But does not solve the problem
Ubiquitous
Background Syncing
Ubiquity daemon, ubd You save changes, ubd sends them to the cloud ubd downloads changes from iCloud Which means....
iCloud Containers
Location for your apps 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 le coordinator Adopt NSFilePresenter to be notied of changes When the le coordinator makes a change, it calls the
le presenter.
APIs
Key-value store Core Data Document
Tip
~/Library/Mobile Documents/
Enabling iCloud
iCloud APIs
Is iCloud Available?
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;
KV Store: Conicts
Last setting into iCloud wins. No conicts, so no conict resolution
NSUbiquitousKeyValueStoreDidChangeExternallyNotification
Internals, briey
Data store les do not get synced Transaction logs written to the iCloud container Transactions are synced and replayed Only changed records are synced
- (void)dealloc { [managedObjectContext__ release]; [managedObjectContext__ release]; [managedObjectContext__ release]; [recipeListController release]; [tabBarController release]; [window release]; [super dealloc]; }
Arbitrary string, iCloud name for persistent store Sub-directory in the iCloud container Optional (but a good idea)
NSPersistentStoreUbiquitousContentURLKey
- (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]; }); });
! ! ! ! !
! ! ! ! !
! ! ! ! !
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification { NSManagedObjectContext* moc = [self managedObjectContext]; [moc mergeChangesFromContextDidSaveNotification:notification]; [[NSNotificationCenter defaultCenter] postNotificationName:@"RefreshAllViews" object:self userInfo:[note userInfo]]; }
Conict Handling
Ubiquitous Documents
UIDocument
UIDocument
Asynchronous block-based reading and writing Auto-saving Flat le and le packages
UIManagedDocument
Concrete subclass of UIDocument Stores document data in Core Data Each instance has its own Core Data stack
UIDocument Concepts
Finding documents
You cant 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 State
UIDocumentStateNormal UIDocumentStateClosed UIDocumentStateInConflict UIDocumentStateSavingError UIDocumentStateEditingDisabled
UIDocumentStateChangedNotification
Subclassing UIDocument
@interface NoteDocument : UIDocument @property (strong, readwrite) NSString *documentText; @end
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); } }); } }]; }
if (!([[self currentDocument] documentState] & UIDocumentStateClosed)) { [[self currentDocument] closeWithCompletionHandler:^(BOOL success) { }]; }
NSMetadataQuery
Congure with an NSPredicate Give it a scope to search Posts notications when results are available
NSMetadataQuery
New search scopes Noties when results have been found Just let it keep running
NSMetadataQueryUbiquitousDocumentsScope NSMetadataQueryUbiquitousDataScope
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?
Look it up on the documents le URL
NSNumber *isIniCloud = nil; if ([fileURL getResourceValue:&isIniCloud forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { }
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 Conicts
UIDocumentStateInConict Can retrieve conicted versions with NSFileVersion Must resolve conicts, because conict versions stick
around until you do
NSFileVersion
+ (NSArray *)unresolvedConflictVersionsOfFileAtURL:(NSURL *)fileURL; + (NSFileVersion *)currentVersionOfFileAtURL:(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]; }
Conict States
UIDocumentStateEditingDisabled UIDocumentStateEditingDisabled & UIDocumentStateInConflict UIDocumentStateEditingDisabled UIDocumentStateNormal
Non-conict updates
UIDocumentStateEditingDisabled UIDocumentStateNormal
Conict Resolution
CloudNotes
https://github.com/atomicbird/CloudNotes