与 Ensembles 同步的核心数据:在本地模型更改之前,不会拉下远程更改
Core Data Syncing with Ensembles: Remote changes aren't pulled down until the local model changes
我有一个 Core Data 应用程序,我正尝试使用 iCloud 作为我的后端来集成 Ensembles 框架。除了在一台设备上进行更改时,我的大部分工作正常,我必须进行更改并将上下文保存在另一台设备上,以便它接收远程更改。
反映数据的tableview符合NSFetchedResultsControllerDelegate
。当本地数据更改并且它获取远程更改时,远程更改会正确反映。
实现一个 "Sync" 按钮,它手动调用 syncWithCompletion
(如下)不会获取更改。
每两分钟触发一次并调用 syncWithCompletion
的计时器不会获取更改。
关闭同步然后再打开确实会获取更改。
重新启动应用程序不会获取更改。
#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);
}
有什么地方出错了吗?
[编辑,因为我的评论太长]
首先,如果我进行本地保存并且云中发生变化(假设它们已经传播 - 有没有办法知道吗?我已经等了很长时间试图排除这种可能性),当我触发手动同步时它也没有被调用。只有当我对本地模型进行更改然后保存我的上下文时才会调用它。我不太清楚那会把我留在哪里。其次查看fetch controller,云端的变化确实没有拉下来。我已打开 CDELoggingLevelVerbose
以继续调查,但我知道我做的某些事情从根本上是错误的,我一定是错过了。
此外,这里有一个很大很大的变化 - 我刚刚从 Ensembles Github 的一个老问题中意识到,在模拟器中触发 iCloud 同步确实有效!不幸的是,我在模拟器中进行所有测试,因为我没有任何设备(我在测试期间使用过多的 iCloud 登录烧毁了我的 iPhone)。会不会是这样?我可以确信这实际上工作正常,但模拟器中有些东西实际上并没有让 iCloud 同步触发吗?
我不清楚为什么它不起作用,但您可以尝试找出一些问题。
首先,尝试从日志中找出本地保存与合并(通过按同步按钮)相比有何不同。 didSaveMergeChangesWithNotification:
委托方法在这两种情况下都会被触发吗?假设云端有变化,应该是。
还值得检查获取结果控制器。有可能更改确实进入了商店,但获取控制器没有接收到它们。一种检查方法是调用 performFetch
并在每次合并结束时重新加载 UI,只是为了测试这是否可能是问题所在。
查看 Ensembles 是否真正获取和合并数据的另一种方法是打开详细日志记录。使用函数CDESetCurrentLogLevel
,传入CDELoggingLevelVerbose
。那会打印出很多关于框架正在做什么的信息,应该会给出线索。
我有一个 Core Data 应用程序,我正尝试使用 iCloud 作为我的后端来集成 Ensembles 框架。除了在一台设备上进行更改时,我的大部分工作正常,我必须进行更改并将上下文保存在另一台设备上,以便它接收远程更改。
反映数据的tableview符合NSFetchedResultsControllerDelegate
。当本地数据更改并且它获取远程更改时,远程更改会正确反映。
实现一个 "Sync" 按钮,它手动调用 syncWithCompletion
(如下)不会获取更改。
每两分钟触发一次并调用 syncWithCompletion
的计时器不会获取更改。
关闭同步然后再打开确实会获取更改。
重新启动应用程序不会获取更改。
#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);
}
有什么地方出错了吗?
[编辑,因为我的评论太长]
首先,如果我进行本地保存并且云中发生变化(假设它们已经传播 - 有没有办法知道吗?我已经等了很长时间试图排除这种可能性),当我触发手动同步时它也没有被调用。只有当我对本地模型进行更改然后保存我的上下文时才会调用它。我不太清楚那会把我留在哪里。其次查看fetch controller,云端的变化确实没有拉下来。我已打开 CDELoggingLevelVerbose
以继续调查,但我知道我做的某些事情从根本上是错误的,我一定是错过了。
此外,这里有一个很大很大的变化 - 我刚刚从 Ensembles Github 的一个老问题中意识到,在模拟器中触发 iCloud 同步确实有效!不幸的是,我在模拟器中进行所有测试,因为我没有任何设备(我在测试期间使用过多的 iCloud 登录烧毁了我的 iPhone)。会不会是这样?我可以确信这实际上工作正常,但模拟器中有些东西实际上并没有让 iCloud 同步触发吗?
我不清楚为什么它不起作用,但您可以尝试找出一些问题。
首先,尝试从日志中找出本地保存与合并(通过按同步按钮)相比有何不同。 didSaveMergeChangesWithNotification:
委托方法在这两种情况下都会被触发吗?假设云端有变化,应该是。
还值得检查获取结果控制器。有可能更改确实进入了商店,但获取控制器没有接收到它们。一种检查方法是调用 performFetch
并在每次合并结束时重新加载 UI,只是为了测试这是否可能是问题所在。
查看 Ensembles 是否真正获取和合并数据的另一种方法是打开详细日志记录。使用函数CDESetCurrentLogLevel
,传入CDELoggingLevelVerbose
。那会打印出很多关于框架正在做什么的信息,应该会给出线索。