Re: iCloud sync per app activation
Re: iCloud sync per app activation
- Subject: Re: iCloud sync per app activation
- From: Martin Hewitson <email@hidden>
- Date: Sun, 05 Feb 2012 10:43:26 +0100
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 (email@hidden)
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:
This email sent to email@hidden