【问题标题】:Merging UIDocument changes for iCloud conflicts合并 iCloud 冲突的 UIDocument 更改
【发布时间】:2018-11-12 04:24:42
【问题描述】:

我已经花了几天时间尝试寻找或自己弄清楚如何在通知 UIDocumentStateChangedNotification 触发且文档状态发生变化时以编程方式合并 UIDocument 更改UIDocumentStateInConflict 设置。

我能找到的所有示例(Apples、Ray Wenderlich 等)都详细说明了提示用户选择版本 方法。 我找不到任何演示以编程方式合并的正确方法。 这让我担心,因为它让我认为它太不稳定而无法信任并且通常被避免作为解决方案? 到目前为止,我在这方面的经验加强了这一立场。

让我详细说明我尝试中的每个问题区域。

1) 为了合并的目的,读取当前文档内容和 NSFileVersion 冲突版本的正确方法是什么? 使用任何带有完成块的东西在同步时真的很麻烦。 UIDocumentopenWithCompletionHandler: 不值得使用。 事实上,作为一项规则,只读 UIDocument 的推荐方法是什么?为什么打开文档只是为了阅读? 我尝试使用 UIDocumentreadFromURL:,这对于当前文档来说很好,但如果我尝试在任何 NSFileVersion 上使用它的 冲突版本它读取当前版本,而不是 URL 上的版本(我使用 MacOS 终端深入挖掘 ../data/.DocumentRevisions-V100/PerUID/... 文件以确认这一点。)。 对于冲突版本,它对我有用的唯一方法是直接读取访问这些文件。 (例如 NSData initWithContentsOfFile:

2) 一旦读入文件的变体,并设法合并,如何正确保存合并? 这个在我能找到的任何地方都没有记录。 我成功的唯一方法是重新使用 NSFileVersion 的冲突文件之一,覆盖它,然后使用 UIDocumentreplaceItemAtURL: 使其成为最新的。 在使用 replaceItemAtURL: 之后,我还尝试使用 UIDocumentrevertToContentsOfURL:,但它只是在没有给出任何理由的情况下崩溃。由于没有它合并似乎可以正常工作,因此我并不担心,但我认为我会将其作为细节包含在内。

3) iPhone/iPad 模拟器 (V10.0) 在我重新启动应用程序之前不会通知冲突。这是可以预料的还是我做错了什么?我问是因为在模拟器的 Debug 菜单下有 Trigger iCloud Sync 可以同步,但在下次应用重新启动之前不会标记冲突。这只是模拟器的限制吗?

谢谢,

【问题讨论】:

    标签: ios icloud uidocument icloud-documents


    【解决方案1】:

    经过几周的测试和了解哪些有效,哪些无效后,我已经简化了我的 UIDocument 合并代码。 我做出的错误假设之一是需要将 UIDocumentrevertToContentsOfURL: 作为解析过程的一部分。 这是一个非常不稳定的 API 调用,我发现最好避免,即使在 @try() 中使用它也不能防止不必要的崩溃。 这让我删除它只是为了看看会发生什么,如果没有它,冲突就会很好地解决。 在 developer.apple.com 上有文档冲突解决的示例代码,暗示应该使用它。 它似乎在 WWDC2018 之后消失了。

    剩下的唯一问题是,如果您有 2 台设备同时打开,您可能会进入竞争状态,因为它们会不断合并文档。

    我之前经历过零冲突版本,尽管文档被标记为冲突,但最近我没有看到这种情况发生。一定是我之前做错了什么。我将代码保留在那里,因为它没有害处。

    我认为在这里值得一提的另一个问题是,如果您是 UIDocument 的新手,请记住它是 UIKit 的一部分,您需要确保在主线程上完成更新。我发现 this useful tip 解决了一些我仍然遇到的问题。

    - (void) foobar {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleDocumentStateChange:)
                                                     name:UIDocumentStateChangedNotification
                                                   object:_myDocument];
    }
    
    - (void) handleDocumentStateChange: (NSNotification *) notification {
        if (_myDocument.documentState & UIDocumentStateInConflict) {
            if (_resolvingConflicts) {
                return;
            }
    
            NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:_myDocument.fileURL];
            if ([conflictVersions count] == 0) {
                return;
            }
            NSMutableArray *docs = [NSMutableArray new];
            [docsData addObject:_myDocument.data]; // Current document data
            _resolvingConflicts = YES;
            for (NSFileVersion *conflictVersion in conflictVersions) {
                MyDocument *myDoc = [[MyDocument alloc] initWithFileURL:conflictVersion.URL];
                NSError *error;
                [myDoc readFromURL:conflictVersion.URL error:&error];
                if ((error == Nil) && (myDoc.data != Nil)) {
                    [docs addObject:myDoc.data];
                }
            }
    
            if ([self mergeDocuments:docs]) {
                [self saveChangesToDocument];
            }
    
            for (NSFileVersion *fileVersion in conflictVersions) {
                fileVersion.resolved = YES;
            }
            [self deleteiCloudConflictVersionsOfFile:_myDocument.fileURL
                                          completion:^(BOOL success){
                                              self.resolvingConflicts = NO;
                                              dispatch_async(dispatch_get_main_queue(), ^{
                                                  // On main thread for UI updates
                                                  [[NSNotificationCenter defaultCenter] postNotificationName:kMyDocsUpdateNotification object:nil];
                                              });
                                          }];
        }
    }
    
    - (void) deleteiCloudConflictVersionsOfFile : (NSURL *) fileURL {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
            NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
            [fileCoordinator coordinateWritingItemAtURL:fileURL
                                                options:NSFileCoordinatorWritingForDeleting
                                                  error:nil
                                             byAccessor:^(NSURL* writingURL) {
                                                 NSError *error;
                                                 if ([NSFileVersion removeOtherVersionsOfItemAtURL:writingURL error:&error]) {
                                                     NSLog(@"deleteiCloudConflictVersionsOfFile: success");
                                                 } else {
                                                     NSLog(@"deleteiCloudConflictVersionsOfFile: error; %@", [error description]);
                                                 }
                                             }];
        });
    }
    

    【讨论】:

      【解决方案2】:

      这里是“为什么打开文档只是为了阅读?”部分的答案。

      您只需要确保读取是“协调的”,即不会与已经被另一个进程打开并且可能有未保存更改的文件发生冲突。

      这是一种遍历 NSDocument url 数组并以同步方式读取每个 URL 的方法,即此例程在读取所有文件之前不会返回。它强制所有未保存更改的文件在任何读取发生之前保存自己。

      // NSArray *urls - the urls of UIDocument files you want to read in bulk
      NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
      NSError *error = nil;
      [coordinator prepareForReadingItemsAtURLs:urls options:NSFileCoordinatorReadingWithoutChanges writingItemsAtURLs:@[] options:0 error:&error byAccessor:^(void (^ _Nonnull completionHandler)(void)) {
          for (NSURL *url in self->_urls) {
              NSError *error = nil;
              [coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL * _Nonnull newURL) {
                  // Read contents of newURL here and process as required
                  // ...
      
              }];
              if (error) {
                  NSLog(@"Error reading: %@ %@", url.path, error.localizedDescription);
              }
          }
          completionHandler();
      }];
      if (error) {
          NSLog(@"Error preparing for read: %@", error.localizedDescription);
      }
      

      【讨论】:

      • 感谢您的回复。这是一个有趣的保留在我的工具箱中。自从我发布这个问题并且有一些运作良好的东西以来,我已经取得了很好的进展。我得到了 UIDocument 的 readFromURL: 来工作,这让我更舒服。由于只有一个进程应该访问其他冲突版本,因此不需要协调。我原来的方法只是按需打开文档。这会导致各种同步问题、崩溃等。由于它是一个单文档实现,因此最好创建一个维护打开副本并且也可以处理冲突合并的单例。
      猜你喜欢
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 2019-07-25
      • 1970-01-01
      • 2022-08-22
      • 1970-01-01
      • 1970-01-01
      • 2014-01-18
      相关资源
      最近更新 更多