12

I have an application for OSX and iOS and both use iCloud for sharing core data between them. When I make changes in the iPhone I can see them at OSX app and the NSPersistentStoreDidImportUbiquitousContentChangesNotification is called at both apps, so the iOS -> Mac works well. But when I make some changes at Mac app I cant see them in the iPhone. The iPhone never calls the NSPersistentStoreDidImportUbiquitousContentChangesNotification. The only way I can get the Mac changes is making some change at iPhone. Then I can see all changes.

iCloud integration in both projects is the same. The core data datamodel is the same. The entitlements are both the same too.

All my code is based on this library: http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

Why is the iPhone not getting Mac changes until I make some change at iPhone? Any ideas?

Another question

In my app when the user enables iCloud I check if is already a file at iCloud. If file exists the user can select to restore core data from the iCloud file or start new copy from the local store. To start a new copy I use this code:

- (void)startNewCopy
{
    [self removeICloudStore];
    [self moveStoreToIcloud];
}

- (void)removeICloudStore
{
    BOOL result;
    NSError *error;
    // Now delete the iCloud content and file
    result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:[self icloudStoreURL]
                                                                             options:[self icloudStoreOptions]
                                                                             error:&error];
    if (!result)
    {
        NSLog(@"Error removing store");
    }
    else
    {
        NSLog(@"Core Data store removed.");
        // Now delete the local file
        [self deleteLocalCopyOfiCloudStore];
    }

}


- (void)deleteLocalCopyOfiCloudStore {
   // We need to get the URL to the store
   NSError *error = nil;
   [[NSFileManager defaultManager] removeItemAtURL:[self localUbiquitySupportURL] error:&error];
}

- (void)moveStoreToIcloud
{
     // Open the store
     NSPersistentStore *sourceStore = [[_persistentStoreCoordinator persistentStores] firstObject];

     if (!sourceStore)
     {
         NSLog(@" failed to add old store");
     }
     else
     {
         NSLog(@" Successfully added store to migrate");
         NSError *error;
         id migrationSuccess = [_persistentStoreCoordinator migratePersistentStore:sourceStore toURL:[self icloudStoreURL] options:[self icloudStoreOptions] withType:NSSQLiteStoreType error:&error];

         if (migrationSuccess)
         {
             NSLog(@"Store migrated to iCloud");

             _persistentStoreCoordinator = nil;
             _managedObjectContext = nil;

             // Now delete the local file
             [self deleteLocalStore];

         }
         else
         {
             NSLog(@"Failed to migrate store: %@, %@", error, error.userInfo);

         }
     }

}

- (void)deleteLocalStore
{
     NSError *error = nil;
     [[NSFileManager defaultManager] removeItemAtURL:[self localStoreURL] error:&error];
}

Doing this from a single device all seems to work well, but when I try this with multiple devices I am getting some errors.

Example:

I have two devices. The first one is not connected to iCloud and the second is connected to iCloud. In the first device when I enable iCloud I choose startNewCopy and then in the second device I get this error:

CoreData: Ubiquity: Librarian returned a serious error for starting downloads Error Domain=BRCloudDocsErrorDomain Code=5 "The operation couldn’t be completed. (BRCloudDocsErrorDomain error 5 - No document at URL)"

The NSPersistentStoreCoordinatorStoresDidChangeNotification is never called before the error.

This is my code for the notification:

- (void)processStoresDidChange:(NSNotification *)notification
{
      NSLog(@"processStoresDidChange");
      // Post notification to trigger UI updates

      // Check type of transition
      NSNumber *type = [notification.userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey];

      //NSLog(@" userInfo is %@", notification.userInfo);
      //NSLog(@" transition type is %@", type);

      if (type.intValue == NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) {

          NSLog(@" transition type is NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted");

      } else if (type.intValue == NSPersistentStoreUbiquitousTransitionTypeAccountAdded) {
          NSLog(@" transition type is NSPersistentStoreUbiquitousTransitionTypeAccountAdded");
      } else if (type.intValue == NSPersistentStoreUbiquitousTransitionTypeAccountRemoved) {
          NSLog(@" transition type is NSPersistentStoreUbiquitousTransitionTypeAccountRemoved");
      } else if (type.intValue == NSPersistentStoreUbiquitousTransitionTypeContentRemoved) {
          NSLog(@" transition type is NSPersistentStoreUbiquitousTransitionTypeContentRemoved");
      }

      [[NSOperationQueue mainQueue] addOperationWithBlock:^ {

          if (type.intValue == NSPersistentStoreUbiquitousTransitionTypeContentRemoved) {

              [self deleteLocalStore];
              _persistentStoreCoordinator = nil;
              _managedObjectContext = nil;

              NSLog(@" iCloud store was removed! Wait for empty store");
          }

          // Refresh user Interface
          [[NSNotificationCenter defaultCenter] postNotificationName:@"notiIcloud" object:@"storeChanged"];
      }];

}

How can I detect that the iCloud content has been removed and avoid getting the error above?

Brian
  • 14,610
  • 7
  • 35
  • 43
user3065901
  • 4,678
  • 11
  • 30
  • 52
  • This doesn't really answer your question, but I gave up on using Core Data with iCloud support a very long time ago. I then wrote my own synching, but it was a bit limited. Then I found Ensembles (http://www.ensembles.io) and have been using it every since. It's been awesome for me. There is a free open-source version. – Jody Hagins Sep 01 '15 at 14:12
  • Thx for the answer, but i prefer to use the standard iCloud support from apple. – user3065901 Sep 01 '15 at 14:53
  • 1
    I wrote that code you are using, what version of iOS, OSX and XCode are you testing with? – Duncan Groenewald Sep 02 '15 at 10:38
  • ios 8, osx 10.10, xcode 6.3. Syncing between iphones works well. I need to know exactly what are the requirements for sync core data between mac and iphone. Maybe i forgot something :S – user3065901 Sep 02 '15 at 16:36
  • Have you tried compiling and running the sample apps with no changes other than the container ID ? They should work out of the box, although bear in mind there are some edge cases not covered in that code. Also check the iCloud folders on your Mac to see if all the transaction files are there, use a terminal session and type something like this `ls -R "Library/Mobile Documents/iCloud~au~com~ossh~iWallet2"` – Duncan Groenewald Sep 03 '15 at 12:08
  • Of course you need to set up App ID's and provisioning profiles but if the iPhone app is working I assume you have done this. Best bet is to compile and test the sample apps I provided and if they don't work I will take a look. – Duncan Groenewald Sep 03 '15 at 12:12

2 Answers2

0

It seems to me you may not be using the appropriate interfaces.

You can move files into and out of iCloud without using NSPersistentStore methods. Often the appropriate action must also be wrapped in a NSFileCoordinator call.

Here is what I do on iOS. toLocal indicates a move to or from local (device) storage:

//
/// Move file between stores (assumed no name clash).
/// Return new URL.
/// Note: cloud file moves are asynchronous.
///
- (NSURL *)moveStoreFile:(NSURL *)url toRootURL:(NSURL *)rootURL toLocal:(BOOL)toLocal
{
    NSURL *newURL = rootURL;
    if (!toLocal)
        newURL = [newURL URLByAppendingPathComponent:@"Documents"];
    newURL = [newURL URLByAppendingPathComponent:url.lastPathComponent];

    [self backupFile:url isLocal:!toLocal completionHandler:
     ^(BOOL success) {

        MRLOG(@"MOVE %s %@ to %s %@", toLocal? "icloud" : "local", [url lastPathComponent],
              toLocal? "local" : "icloud", [newURL lastPathComponent]);

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
        ^{
           NSError *error;
           // setUbiquitous does file coordination
           BOOL msuccess = [[NSFileManager defaultManager]
                           setUbiquitous:!toLocal itemAtURL:url destinationURL:newURL error:&error];

           dispatch_async(dispatch_get_main_queue(),
           ^{
              if (!msuccess)
                  MRLOG(@"move failed: %@", error);
              [self.delegate moveStoreFileCompletedWithStatus:success error:error];
           });
        });
    }];

    return newURL;
}

Deleting requires explicit file coordination:

///
/// Purge backup file (really delete it).
/// Note: cloud deletes are asynchronous.
///
- (void)purgeFile:(NSURL *)url isLocal:(BOOL)isLocal
{
    MRLOG(@"PURGE %s %@", isLocal? "local" : "icloud", [url lastPathComponent]);

    if (isLocal)
        [self removeFile:url];
    else
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
        ^{
            NSError *coordinationError;
            NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];

            [coordinator coordinateWritingItemAtURL:url options:NSFileCoordinatorWritingForDeleting
                                                    error:&coordinationError
                                     byAccessor:
             ^(NSURL* writingURL)
             {
                 [self removeFile:writingURL];
             }];
            if (coordinationError) {
                MRLOG(@"coordination error: %@", coordinationError);
                [(SSApplication *)[SSApplication sharedApplication] fileErrorAlert:coordinationError];
            }
        });
}

To detect changes to files you need to use NSMetadataQuery and add an observer for NSMetadataQueryDidUpdateNotification.

MichaelR
  • 1,681
  • 15
  • 28
0

I had a similar issue syncing iOS with my Mac.

This error 5 actually means there is an issue with your store.

You can try these steps:

  • On iOS you need to reset the content of your Simulator and also clear your iCloud content (for your app only).

You can reset your iCloud content by calling (sorry I have it in Swift):

try! NSPersistentStoreCoordinator.removeUbiquitousContentAndPersistentStoreAtURL(storeURL, options: self.options)
  • On Mac OS you need to remove the content of this folder "CoreDataUbiquitySupport/YourAppId". This is basically your Core Data Persistent Store (e.g.: SQLite file).

This issue happens when the Model (xcdatamodeld file) changed and the existing data hasn't been cleared. You end up having a new model associated to data generated under an older model and you are trying to sync that mix across devices...

In the future, to avoid Model related issue, have a look at Core Data Model Versioning and Data Migration. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html

Hope it solves your problem.

Romain
  • 649
  • 1
  • 5
  • 18