合并UIDocument更改iCloud冲突

时间:2018-06-02 04:01:03

标签: ios icloud uidocument icloud-documents

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

我可以找到的所有示例(苹果,Ray Wenderlich等等)都详细说明提示用户选择版本方法。 我找不到任何能够以编程方式合并的正确方法。 这让我很担心,因为它让我觉得信任太不稳定了,通常作为一种解决方案被避免了? 到目前为止,我对它的体验加强了这一地位。

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

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

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

3)在重新启动应用程序之前,iPhone / iPad模拟器(V10.0)不会通知冲突。这是预期的还是我做错了什么?我问,因为在模拟器的调试菜单下有触发iCloud Sync ,它会同步,但冲突不会被标记,直到下一个应用程序重新启动。这仅仅是模拟器的限制吗?

谢谢,

2 个答案:

答案 0 :(得分:0)

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

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

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

// 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);
}

答案 1 :(得分:0)

经过几周的测试,我已经简化了我的UIDocument合并代码,并了解哪些有效,哪些无效。 我做出的一个错误假设是,需要在解决过程中包含 UIDocument revertToContentsOfURL: 。 这是一个非常不稳定的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]);
                                             }
                                         }];
    });
}