OK, I've perhaps partially answered this myself, but I'd still be very happy to hear any opinions on this. Below is a sample of code which handles the main bits of my solution (error handling etc removed).
Does this look reasonable? Still it doesn't handle the question: how to deal with changes that occurred locally while iCloud syncing was disabled? Cheers, Martin On 5, Feb, 2012, at 09:43 AM, Martin Hewitson wrote: >>> >> >> I'm finishing up a shoebox app now and I do have the option to store things >> in iCloud or not. My eventual solution to this was to have a preference >> screen in the app with a single "enable iCloud' switch. If you flip it from >> off to on, or on to off, you get a section of buttons to hit asking how you >> want to perform the transition (eg when transitioning to the cloud you can >> merge local to cloud, use cloud or use local), it then gives you a >> confirmation box before you do it. I failed to find a really good way to do >> this in preferences, so I put it in the app itself, there are just too many >> questions about how you want to perform the migration which I think need to >> be asked then. >> > > Hi Roland, > > I'm starting to think about how to implement this in my OS X app. Would you > be willing to share any clues as the correct strategies? How to merge the > managed object contexts? How to make sure that changes accumulated locally > while not syncing with iCloud are then transferred to the cloud? If I want to > replace the 'truth' in the cloud with the local store, how do I make sure > that all the necessary transactions exist so that other clients update > themselves? > > The reason I'm worried about the correct way to do this is due to the > following scenarios: > > 1) Starting from a completely new app > > Here I've been able to configure and sync a persistent store as long as I > have the ubiquity keys in the store options the very first time the app runs. > If I want to change the 'truth' in the cloud, or start from a fresh > persistent store, I haven't found a reliable way to do that. Deleting the > local store doesn't work for me. Essentially the only thing that works is the > solution in point 2) below. > > 2) Starting from an existing app with an existing persistent store. > > One thing I've noticed (at least on OS X) is that if I have an existing core > data store which was previously being used without iCloud, and then I add the > iCloud ubiquity keys to the store options, the contents of the store (the > full sql data store) are not pushed to the cloud container when the app > starts. In order to get this to work I've need to use > -migratePersistentStore:toURL:options:withType:error: to move the existing > store to a new URL. Then the full store is uploaded and everything works from > there. > > So, switching between these two states (with a user option) seems tricky to > me. At least I can't see a good way to handle. > - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (__persistentStoreCoordinator) { return __persistentStoreCoordinator; } NSManagedObjectModel *mom = [self managedObjectModel]; if (!mom) { return nil; } NSURL *applicationFilesDirectory = [self applicationSupportDirectory]; NSPersistentStoreCoordinator *coordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom] autorelease]; // Store URL NSString *storeName = @"MyApp.sqlstoredata"; NSURL *storeURL = [applicationFilesDirectory URLByAppendingPathComponent:storeName]; // Ubiquity container NSString *containerID = @"...myid..."; NSURL *contentURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:containerID]; NSLog(@"iCloud content URL: %@", contentURL); // store options NSMutableDictionary *options = [[[NSMutableDictionary alloc] init] autorelease]; if (contentURL != nil && [self shouldSyncWithiCloud]) { [options setObject:storeName forKey:NSPersistentStoreUbiquitousContentNameKey]; [options setObject:contentURL forKey:NSPersistentStoreUbiquitousContentURLKey]; } error = nil; NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]; __persistentStoreCoordinator = [coordinator retain]; return __persistentStoreCoordinator; } - (NSManagedObjectContext *)managedObjectContext { if (__managedObjectContext) { return __managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } if ([self shouldSyncWithiCloud]) { __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [__managedObjectContext performBlockAndWait:^{ [__managedObjectContext setPersistentStoreCoordinator: coordinator]; [__managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; }]; } else { __managedObjectContext = [[NSManagedObjectContext alloc] init]; [__managedObjectContext setPersistentStoreCoordinator: coordinator]; } return __managedObjectContext; } // An action triggered by a check box - (IBAction)iCloudSyncStateAction:(id)sender { if ([self shouldSyncWithiCloud]) { // We weren't syncing before but we are now. So we need to ask the user if they want to merge // the data from iCloud NSAlert *alert = [NSAlert alertWithMessageText:@"Do you want to merge your trips with iCloud?" defaultButton:@"Merge" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Your trips on this Mac will be uploaded and merged with the trips stored in iCloud."]; [alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(mergeWithiCloudAlertDidEnd:returnCode:contextInfo:) contextInfo:NULL]; } else { // we were syncing and now we're not [self restartManagedObjectContext]; } } - (void) mergeWithiCloudAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; { if (returnCode == NSAlertAlternateReturn) { // cancel // set sync state back to NO [self setShouldSyncWithiCloud:NO]; return; } // save any changes we have currently NSError *error = nil; if (![[self managedObjectContext] commitEditing]) { NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd)); } if (![[self managedObjectContext] save:&error]) { [[NSApplication sharedApplication] presentError:error]; return; } // restart the MOC and PSC [self restartManagedObjectContext]; } - (void) restartManagedObjectContext { [self willChangeValueForKey:@"managedObjectContext"]; [__managedObjectContext release]; __managedObjectContext = nil; [__persistentStoreCoordinator release]; __persistentStoreCoordinator = nil; // just to force the moc to be recreated [self managedObjectContext]; [self didChangeValueForKey:@"managedObjectContext"]; } - (void)setShouldSyncWithiCloud:(BOOL)state { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setValue:[NSNumber numberWithBool:state] forKey:MHSyncWithiCloud]; [defaults synchronize]; } - (BOOL)shouldSyncWithiCloud { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; return [[defaults valueForKey:MHSyncWithiCloud] boolValue]; } _______________________________________________ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com