核心数据与 Ensembles 同步:在本地模型更改之前,不会拉下远程更改

Posted

技术标签:

【中文标题】核心数据与 Ensembles 同步:在本地模型更改之前,不会拉下远程更改【英文标题】:Core Data Syncing with Ensembles: Remote changes aren't pulled down until the local model changes 【发布时间】:2017-12-17 21:51:09 【问题描述】:

我有一个 Core Data 应用程序,我正在尝试使用 iCloud 作为我的后端来集成 Ensembles 框架。除了在一台设备上进行更改时,我的大部分工作都正常工作,我必须进行更改并将上下文保存在另一台设备上,以便它接收远程更改。

反映数据的tableview符合NSFetchedResultsControllerDelegate。当本地数据发生变化并拾取远程更改时,远程更改会正确反映。

实现手动调用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);

任何想法我哪里出错了?

[编辑,因为我的评论太长]

首先,didSaveMergeChangesWithNotificationnot 如果我进行本地保存并且云中有更改(假设它们已经传播 - 有没有办法知道?我已经等了很久一段时间来尝试排除这种情况),当我触发手动同步时它也不会被调用。只有当我对本地模型进行更改然后保存我的上下文时才会调用它。我不知道这会把我留在哪里。其次,查看 fetch 控制器,云端的变化确实没有拉下来。我已经打开CDELoggingLevelVerbose 继续调查,但我知道我做错了一些我必须遗漏的事情。

另外,这是一个很大的变化——我刚刚从 Ensembles Github 中的一个老问题中意识到,在模拟器中触发 iCloud 同步确实有效!不幸的是,我在模拟器中进行了所有测试,因为我没有任何设备(我在测试期间用太多的 iCloud 登录烧毁了我的 iPhone)。会是这样吗?我可以确信这实际上工作正常,但是模拟器中的某些东西实际上并没有让 iCloud 同步触发吗?

【问题讨论】:

您是否被可怕的“cloud to butt”扩展所击中,或者您是否制作了一个名为“ibutt”的自定义后端?如果您只是使用 iCloud Drive,一种可能的解释是同步其文件需要一段时间。这是 Ensembles 无法真正影响的东西,除了要求下载它们。 @DrewMcCormack 哈,抱歉,是的,我确实安装了“云对接”。我编辑了这个问题,我会在一分钟内在你的答案中尝试你的建议。通常我觉得这个扩展很有趣。今天不行。 如果你在模拟器中测试,确实有可能是没有正确下载文件。我听说过用户在模拟器中遇到问题的报告。您可以尝试 Xcode 调试菜单来触发 iCloud 同步文件。如果没有在设备上或使用 Dropbox 进行测试,我只能建议使用 CloudKit 的 Ensembles 2,但它需要付费。如果你能拿到一个 iPod touch,也许就可以进行测试了。 【参考方案1】:

我不清楚为什么它不起作用,但是您可以尝试找出一些事情。

首先,尝试从日志中找出执行本地保存与仅合并(通过按同步按钮)时的不同之处。在这两种情况下都会触发didSaveMergeChangesWithNotification: 委托方法吗?假设云中有变化,它应该。

检查获取结果控制器也是值得的。有可能更改确实进入了商店,但 fetch 控制器没有提取它们。一种检查方法是调用 performFetch 并在每次合并结束时重新加载 UI,只是为了测试这是否是问题所在。

查看 Ensembles 是否真正获取和合并数据的另一种方法是打开详细日志记录。使用函数CDESetCurrentLogLevel,传入CDELoggingLevelVerbose。这将打印很多关于框架正在做什么的信息,并且应该提供线索。

【讨论】:

这似乎都是使用真实设备而不是模拟器来解决的。耸耸肩。

以上是关于核心数据与 Ensembles 同步:在本地模型更改之前,不会拉下远程更改的主要内容,如果未能解决你的问题,请参考以下文章

让 Ensembles Idiomatic OSX App 与 iCloud 同步

全局标识符? - iCloud + Core Data + Ensembles - 删除对象时重复

在使用 Ensembles 进行 CoreData 和 iCloud 同步之前,我是不是需要任何 iCloud 设置?

将 Ensembles 添加到现有 Coredata / iCloud 应用程序的步骤

集成和核心数据轻迁移

将本地核心数据数据库与 Web 服务同步