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 protected])
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 [email protected]