Слияние изменений UIDocument для конфликтов iCloud - PullRequest
0 голосов
/ 02 июня 2018

Я провел несколько дней, пытаясь найти или выяснить для себя, как программно объединить UIDocument изменения, когда уведомление UIDocumentStateChangedNotification и состояние документа имеют UIDocumentStateInConflict set.

Все примеры, которые я могу найти (Яблоки, Рэй Вендерлих и т. Д. И т. Д.), Подробно описывают приглашение пользователя для выбора метода .Я не могу найти ни одного, который продемонстрировал бы правильный способ программного слияния.Это беспокоит меня, так как заставляет меня думать, что доверять слишком странно и, как правило, его избегают в качестве решения?Мой опыт работы с ним до сих пор укрепляет эту позицию.

Позвольте мне подробно рассказать о каждой проблемной области в моих попытках.

1) Как правильно читать содержимое текущего документа и NSFileVersion версии конфликта с целью слияния?Использование чего-либо с блоком завершения действительно беспорядочно в синхронизации. UIDocument openWithCompletionHandler: не заманчиво для использования.На самом деле, как правило, каков рекомендуемый способ только для чтения UIDocument ?Зачем открывать документ только для чтения?Я пытался использовать UIDocument readFromURL: , что хорошо для текущего документа, но если я пытаюсь использовать его для любого из конфликтов NSFileVersion версии, в которых он читает текущую версию, а не версию по URL-адресу (для подтверждения этого я использовал терминал MacOS, чтобы углубиться в файлы ../ data / .DocumentRevisions-V100 / PerUID / ...).Для конфликтных версий единственный способ, которым он работает для меня - это прямой доступ к этим файлам.(например, NSData initWithContentsOfFile: )

2) После того, как после прочтения вариантов файла и после слияния, как правильно сохранитьсливаться?Этот действительно не зарегистрирован нигде, где я могу найти.Единственный подход, с которым мне удалось, - это повторно использовать один из файлов конфликтов NSFileVersion , перезаписать его, а затем использовать UIDocument * replaceItemAtURL: чтобы сделать это актуальным.Я также пытался использовать UIDocument revertToContentsOfURL: после использования replaceItemAtURL: , но он просто вылетает без указания причины.Поскольку слияние, кажется, работает нормально без него, я не волнуюсь, но подумал, что я бы включил это как деталь.

3) Симулятор iPhone / iPad (V10.0) не уведомляет о конфликтах, покаЯ перезапускаю приложение.Это ожидать или я делаю что-то не так?Я спрашиваю, потому что в меню Debug симулятора есть Trigger iCloud Sync , который синхронизируется, но конфликты не отмечаются до следующей перезагрузки приложения.Это только ограничение симулятора?

Спасибо,

Ответы [ 2 ]

0 голосов
/ 11 июня 2018

Я упростил свой код слияния UIDocument после нескольких недель тестирования и изучения того, что работает, а что нет.Одно из неправильных предположений, которые я сделал, заключалось в том, что было необходимо включить UIDocument * revertToContentsOfURL: как часть процесса разрешения.Это очень нестабильный вызов 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]);
                                             }
                                         }];
    });
}
0 голосов
/ 09 июня 2018

Вот ответ на часть «Зачем открывать документ только для чтения?».

Вам просто нужно убедиться, что чтение «скоординировано», то есть не происходит столкновений с файлами, которые уже открыты другим процессом и могут иметь несохраненные изменения.

Вот способ перебора массиваNSDocument 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);
}
...