I have a Core Data application with which I am trying to integrate the Ensembles framework using iCloud as my backend. I have most things working except that when making a change on one device, I have to make a change and save the context on the other device for it to pick up the remote changes.
The tableview that reflects the data conforms to NSFetchedResultsControllerDelegate
. When the local data changes and it picks up the remote changes, the remote changes are reflected correctly.
Implementing a "Sync" button, which manually calls syncWithCompletion
(below) does not pick up the changes.
A timer that fires off every two minutes, calling syncWithCompletion
, does not pick up the changes.
Turning sync off and then on again does pick up the changes.
Restarting the app does not pick up the changes.
#pragma mark - ENSEMBLES
- (void)setupEnsembles {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
// set the sync UI on
[[NSNotificationCenter defaultCenter] postNotificationName:@"setSyncUIOn" object:nil];
// setup ensemble
self.cloudFileSystem = [[CDEICloudFileSystem alloc] initWithUbiquityContainerIdentifier:nil];
self.ensemble = [[CDEPersistentStoreEnsemble alloc] initWithEnsembleIdentifier:@"RecordStore"
persistentStoreURL:self.storeURL
managedObjectModelURL:[self modelURL]
cloudFileSystem:self.cloudFileSystem];
self.ensemble.delegate = self;
// Listen for local saves, and trigger merges
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(localSaveOccurred:)
name:CDEMonitoredManagedObjectContextDidSaveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cloudDataDidDownload:)
name:CDEICloudFileSystemDidDownloadFilesNotification
object:nil];
[self syncWithCompletion:NULL];
// configure a timer to trigger a merge every two minutes
if (!self.ensemblesSyncTimer) {
self.ensemblesSyncTimer = [NSTimer scheduledTimerWithTimeInterval:120.0
target:self
selector:@selector(performScheduledSync:)
userInfo:nil
repeats:YES];
}
}
- (void)performScheduledSync:(NSTimer*)aTimer {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[self syncWithCompletion:NULL];
}
- (void)syncWithCompletion:(void(^)(void))completion {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
// set the sync UI on
[[NSNotificationCenter defaultCenter] postNotificationName:@"setSyncUIOn" object:nil];
// this checks to make sure there is an ensemble, because this method
// can be called without knowing whether ensembles is enabled or not
if (self.ensemble) {
if (coreDataDebug==1) { NSLog(@"there is an ensemble, going to leech or merge"); }
if (!self.ensemble.isLeeched) {
if (coreDataDebug==1) { NSLog(@"leeching"); }
[self.ensemble leechPersistentStoreWithCompletion:^(NSError *error) {
if (error) NSLog(@"Error in leech: %@", [error localizedDescription]);
// set the last synced date
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
[dateFormatter setLocale:[NSLocale currentLocale]];
[[NSUserDefaults standardUserDefaults] setObject:[dateFormatter stringFromDate:[NSDate date]]
forKey:@"iCloudLastSyncDate"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"setSyncUIOff" object:nil];
object:nil];
if (completion) {completion();}
}];
}
else {
if (coreDataDebug==1) { NSLog(@"merging"); }
[self.ensemble mergeWithCompletion:^(NSError *error) {
if (error) NSLog(@"Error in merge: %@", [error localizedDescription]);
// set the last synced date
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
[dateFormatter setLocale:[NSLocale currentLocale]];
[[NSUserDefaults standardUserDefaults] setObject:[dateFormatter stringFromDate:[NSDate date]]
forKey:@"iCloudLastSyncDate"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"setSyncUIOff" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"recordCollectionNeedsRefresh"
object:nil];
if (completion) {completion();}
}];
}
}
}
- (void)localSaveOccurred:(NSNotification *)notif {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[self syncWithCompletion:NULL];
}
- (void)cloudDataDidDownload:(NSNotification *)notif {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[self syncWithCompletion:NULL];
}
- (void)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble didSaveMergeChangesWithNotification:(NSNotification *)notification {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[_context performBlockAndWait:^{
[_context mergeChangesFromContextDidSaveNotification:notification];
}];
}
- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
return [objects valueForKeyPath:@"uniqueIdentifier"];
}
- (void)removeEnsembles {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[self disconnectFromSyncServiceWithCompletion:NULL];
}
- (void)disconnectFromSyncServiceWithCompletion:(CDECodeBlock)completion {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
[self.ensemble deleechPersistentStoreWithCompletion:^(NSError *error) {
self.ensemble.delegate = nil;
[self.ensemble dismantle];
self.ensemble = nil;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"iCloudLastSyncDate"];
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"iCloudSyncingEnabled"];
[self.ensemblesSyncTimer invalidate];
self.ensemblesSyncTimer = nil;
if (completion) completion();
}];
}
- (void)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble didDeleechWithError:(NSError *)error {
if (ensemblesDataDebug==1) { printf("Running %s\n", [NSStringFromSelector(_cmd) UTF8String]); }
NSLog(@"Store did deleech with error: %@", error);
}
Any ideas where I'm going wrong?
[EDIT since my comment is too long]
Firstly, didSaveMergeChangesWithNotification
does not get called if I do a local save and there are changes in the cloud (assuming they have propagated - is there a way to know? I've waited quite a while to try to rule that out), nor does it get called when I trigger a manual sync. It is only called when I make a change to a local model and then save my context. I don't quite know where that leaves me. Secondly, checking the fetch controller, the changes in the cloud are indeed not pulled down. I've turned on CDELoggingLevelVerbose
to continue investigating, but I know there's something I'm doing fundamentally wrong that I must be missing.
Also, here is a big, big change - I've just realized from an old issue in the Ensembles Github that triggering an iCloud sync in the simulator actually works! Unfortunately, I'm doing all my testing in the simulator as I don't have any devices (I burned my iPhone with too many iCloud logins during testing). Could this be it? Could I be confident that this is actually working normally, but there is something in the simulator that is not actually letting iCloud sync trigger?