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

Reply via email to