Mac / iPhone app - syncing core data to iCloud & devices (using core data)
Asked Answered
A

3

6

I am working an iPhone app and a Mac app that use Core Data.

I would like to have these 2 apps synchronise their databases via iCloud storage.

I have made adjustments to the implementations of the managedObjectContext & persistentStoreCoordinator & added mergeiCloudChanges - from the updated Recipes example code:

#pragma mark -
#pragma mark Core Data stack

// this takes the NSPersistentStoreDidImportUbiquitousContentChangesNotification
// and transforms the userInfo dictionary into something that
// -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] can consume
// then it posts a custom notification to let detail views know they might want to refresh.
// The main list view doesn't need that custom notification because the NSFetchedResultsController is
// already listening directly to the NSManagedObjectContext
- (void)mergeiCloudChanges:(NSNotification*)note forContext:(NSManagedObjectContext*)moc {

    NSLog(@"merging iCloud stuff");

    [moc mergeChangesFromContextDidSaveNotification:note]; 

    NSNotification* refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self  userInfo:[note userInfo]];

    [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
}

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext
{
    if (managedObjectContext != nil)
    {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil)
    {
        if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0")) {
            NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

            [moc performBlockAndWait:^{
                [moc setPersistentStoreCoordinator: coordinator];

                [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
            }];
            managedObjectContext = moc;
        } else {
            managedObjectContext = [[NSManagedObjectContext alloc] init];
            [managedObjectContext setPersistentStoreCoordinator:coordinator];
        }

    }
    return managedObjectContext;
}

// NSNotifications are posted synchronously on the caller's thread
// make sure to vector this back to the thread we want, in this case
// the main thread for our views & controller
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {


     NSManagedObjectContext* moc = [self managedObjectContext];

    // this only works if you used NSMainQueueConcurrencyType
    // otherwise use a dispatch_async back to the main thread yourself
    [moc performBlock:^{
        [self mergeiCloudChanges:notification forContext:moc];
    }];
}


/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
 */
- (NSManagedObjectModel *)managedObjectModel {

    if (managedObjectModel != nil) {
        return managedObjectModel;
    }
    managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];    
    return managedObjectModel;
}





- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator__ != nil) {
        return persistentStoreCoordinator__;
    }

    // assign the PSC to our app delegate ivar before adding the persistent store in the background
    // this leverages a behavior in Core Data where you can create NSManagedObjectContext and fetch requests
    // even if the PSC has no stores.  Fetch requests return empty arrays until the persistent store is added
    // so it's possible to bring up the UI and then fill in the results later
    persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];


    // prep the store path and bundle stuff here since NSBundle isn't totally thread safe
    NSPersistentStoreCoordinator* psc = persistentStoreCoordinator__;
     NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"MyApp.sqlite"];

    // do this asynchronously since if this is the first time this particular device is syncing with preexisting
    // iCloud content it may take a long long time to download
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSFileManager *fileManager = [NSFileManager defaultManager];

        NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
        // this needs to match the entitlements and provisioning profile
        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
        NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"MyApp"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

        NSLog(@"cloudURL: %@", cloudURL);        

        //  The API to turn on Core Data iCloud support here.
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"xxxxxxxx.com.me.MyApp", 
                                 @"MyApp", 
                                 cloudURL, 
                                 NSPersistentStoreUbiquitousContentURLKey, 
                                 [NSNumber numberWithBool:YES], 
                                 NSMigratePersistentStoresAutomaticallyOption, 
                                 [NSNumber numberWithBool:YES], 
                                 NSInferMappingModelAutomaticallyOption,
                                 nil];

        NSError *error = nil;

        [psc lock];
        if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.

             Typical reasons for an error here include:
             * The persistent store is not accessible
             * The schema for the persistent store is incompatible with current managed object model
             Check the error message to determine what the actual problem was.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }    
        [psc unlock];

        // tell the UI on the main thread we finally added the store and then
        // post a custom notification to make your views do whatever they need to such as tell their
        // NSFetchedResultsController to -performFetch again now there is a real store
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"asynchronously added persistent store!");
            [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil];
        });
    });

    return persistentStoreCoordinator__;
}

I can see files appear in my "/Users/me/Library/Mobile Documents" directory when i build/run myapp.
But I have no idea if it is syncing over to the iCloud storage - and obviously the data between the iphone and mac is not synced.
Are there other methods I need to implement to make the data move to the cloud?
And is there any way for me to view what documents are actually on the iCloud storage?

Anastos answered 16/11, 2011 at 15:44 Comment(0)
M
3

Here is a quick partial answer.

You can see what is stored in iCloud:

On the Mac:

System Preferences.app -> iCloud -> click on 'Manage...' you will then see a list of all apps that have documents stored Mac OS X or iOS.

On iOS:

Preferences -> iCloud -> Archive & Backup -> option below Space used you will then see a list of all apps that have documents stored Mac OS X or iOS.

As long as you are using NSFileManager's setUbiquitous: itemAtURL: destinationURL: error:the documents should be getting sent to iCloud for you and showing up on other devices.

Martyr answered 18/11, 2011 at 21:55 Comment(5)
Hi Mike, thanks for your reply. I have checked my iCloud storage via sys prefs. It looks like nothing is copying over. Well keep looking. Any other tips? Also yes I am using NSFileManager's setUbiquitousAnastos
Ahh actually no I am not using "setUbiquitous: itemAtURL: destinationURL: error:" - i am using "addPersistentStoreWithType " and passing it a dictionary of options which contains NSPersistentStoreUbiquitousContentNameKey Is this the same idea? I will have read... Thanks againAnastos
I have noticed that my app hangs at this line: if ([[NSFileManager defaultManager] setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) { so i stop the app from Xcode, and then this appears in the Console app: librariand: client connection is invalid: Connection invalid Any ideas?Anastos
The connection invalid message just means the app went away so there is no more connection. If you are using a document based app you need to use setUbiquitous:.... When I was having issues I added NSLog(@"container == %@", [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container]); to one of the init methods in the app to see if I was even getting a URL. You should also make sure you aren't trying to move the document before it has been written to disk somewhere else first.Martyr
Hi Mark thanks for your help, I have changed the way my app works now and I can finally see some sort of synchronisation happening on both my iPhone and mac. Although i can't say this is exactly smooth. Am going to ask a new question regarding my new issues! Thanks again for your help!Anastos
S
1

Another partial answer. Take a look at : http://www.raywenderlich.com/6015/beginning-icloud-in-ios-5-tutorial-part-1 if you have not yet. I am working on the same thing as you namely, Mac App and iOS app sharing data. Good Luck. Mark

I just learned about : http://mentalfaculty.tumblr.com/archive Look for "Under The Sheets" CoreData and iCloud. Check it out!

Stereoscopy answered 5/3, 2012 at 20:44 Comment(0)
U
0

OK my code look a bit different, I have it in a separate class to reuse it for all my projects. Nevertheless if iCloud is enabled (URLForUbiquityContainerIdentifier:nil return not nil) I setup my NSPersistentStoreCoordinator like this:

// ---- iCloud Setup 

// fist container in entitlements
NSURL *iCloudDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; 

// if iCloud is enabled setup the store
if (iCloudDirectoryURL) {

    __iCloudEnabled = true;

    NSLog(@"iCloud:%@", [iCloudDirectoryURL absoluteString]);
    // AppDelegate has to provide the contentnamekey
    NSString *contentNameKey = [appDelegate dataStoreContentNameKey];

     options = [NSDictionary dictionaryWithObjectsAndKeys:contentNameKey, NSPersistentStoreUbiquitousContentNameKey, iCloudDirectoryURL, NSPersistentStoreUbiquitousContentURLKey, nil];
}

I miss where you setup the NSPersistentStoreUbiquitousContentNameKey.

The second is clear i guess, this works only on the device and your App ID need iCloud enabled.

Underground answered 23/3, 2012 at 7:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.