3

我现在花了几天时间试图找到或自己弄清楚如何在通知UIDocumentStateChangedNotification触发并且文档的状态设置了UIDocumentStateInConflict时以编程方式合并UIDocument更改。

我能找到的所有示例(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可以同步,但直到下一次应用程序重新启动时才会标记冲突。这只是模拟器的限制吗?

谢谢,

4

2 回答 2

1

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

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

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

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

- (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]);
                                             }
                                         }];
    });
}
于 2018-06-11T05:30:03.750 回答
0

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

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

这是一种遍历 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);
}
于 2018-06-09T06:46:56.403 回答