Как использовать / тестировать изменения пользовательской информации NSProgress дочернего экземпляра NSProgress

Я реализую NSProgress Поддержка в библиотеке, и я написал несколько модульных тестов, чтобы проверить, что все работает правильно. Хотя в идеале я хотел бы иметь возможность передавать некоторые дополнительные метаданные (userInfo ключи не используются NSProgress сам, но для пользователей моего API, чтобы потреблять), а пока я просто пытаюсь получить localizedDescription а также localizedAdditionalDescription работать так, как говорит документация, они должны. Поскольку метод, который я тестирую, извлекает файлы из архива, я установил kind в NSProgressKindFile и установить различные ключи, связанные с файловыми операциями (например, NSProgressFileCompletedCountKey).

Я ожидаю, когда я наблюдаю изменения в localizedDescription с KVO, что я буду видеть такие обновления:

Обработка "Тестового файла A.txt"

Обработка "Тестового файла B.jpg"

Обработка "Тестового файла C.m4a"

Когда я останавливаюсь на точке останова и po localizedDescription на работника NSProgress пример (childProgress ниже), это фактически то, что я вижу. Но когда мои тесты запускаются, все, что они видят, это следующее, подразумевая, что он не видит ничего из userInfo ключи, которые я установил:

0% выполнено

0% выполнено

53% завершено

Выполнено на 100%

Выполнено на 100%

Похоже, userInfo ключи я установил на ребенка NSProgress экземпляр не передается его родителю, даже если fractionCompleted делает. Я делаю что-то неправильно?

Ниже приведены некоторые фрагменты абстрактного кода, но вы также можете загрузить коммит с этими изменениями из GitHub. Если вы хотите воспроизвести это поведение, запустите -[ProgressReportingTests testProgressReporting_ExtractFiles_Description] а также -[ProgressReportingTests testProgressReporting_ExtractFiles_AdditionalDescription] контрольные примеры.

В моем тестовом классе:

static void *ProgressContext = &ProgressContext;

...

- (void)testProgressReporting {
    NSProgress *parentProgress = [NSProgress progressWithTotalUnitCount:1];
    [parentProgress becomeCurrentWithPendingUnitCount:1];

    [parentProgress addObserver:self
                     forKeyPath:NSStringFromSelector(@selector(localizedDescription))
                        options:NSKeyValueObservingOptionInitial
                        context:ProgressContext];

    MyAPIClass *apiObject = // initialize
    [apiObject doLongRunningThing];

    [parentProgress resignCurrent];
    [parentProgress removeObserver:self
                        forKeyPath:NSStringFromSelector(@selector(localizedDescription))];
}


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context
{
    if (context == ProgressContext) {
        // Should refer to parentProgress from above
        NSProgress *notificationProgress = object;

        [self.descriptionArray addObject:notificationProgress.localizedDescription];
    }
}

Затем в моем классе в тесте:

- (void) doLongRunningThing {
    ...
    NSProgress *childProgress = [NSProgress progressWithTotalUnitCount:/* bytes calculated above */];
    progress.kind = NSProgressKindFile;
    [childProgress setUserInfoObject:@0
                              forKey:NSProgressFileCompletedCountKey];
    [childProgress setUserInfoObject:@(/*array count from above*/)
                              forKey:NSProgressFileTotalCountKey];

    int counter = 0;

    for /* Long-running loop */ {
        [childProgress setUserInfoObject: // a file URL
                                  forKey:NSProgressFileURLKey];

        // Do stuff

        [childProgress setUserInfoObject:@(++counter)
                                  forKey:NSProgressFileCompletedCountKey];
        childProgress.completedUnitCount += myIncrement;
    }
}

В то время я увеличиваю childProgress.completedUnitCount Это то, как выглядит userInfo в отладчике. Все поля, которые я установил, представлены:

> po childProgress.userInfo

{
    NSProgressFileCompletedCountKey = 2,
    NSProgressFileTotalCountKey = 3,
    NSProgressFileURLKey = "file:///...Test%20File%20B.jpg"; // chunk elided from URL
}

Когда каждое уведомление KVO возвращается, вот как notificationProgress.userInfo выглядит:

> po notificationProgress.userInfo

{
}

2 ответа

Решение

Хорошо, у меня была возможность еще раз взглянуть на код с большим количеством кофе в моей системе и большим количеством времени в моих руках. Я на самом деле вижу, как это работает.

В вашем методе testProgressReporting_ExtractFiles_AdditionalDescription я изменил код так:

NSProgress *extractFilesProgress = [NSProgress progressWithTotalUnitCount:1];
[extractFilesProgress setUserInfoObject:@10 forKey:NSProgressEstimatedTimeRemainingKey];
[extractFilesProgress setUserInfoObject:@"Test" forKey:@"TestKey"];

И тогда в наблюдаем ValueForKeyPath я напечатал эти объекты:

po progress.userInfo {
NSProgressEstimatedTimeRemainingKey = 10;
TestKey = Test;
}

po progress.localizedAdditionalDescription
0 of 1 — About 10 seconds remaining

Вы можете увидеть значения ключей, которые я добавил, и localizedAdditionalDescription был создан на основе этих записей (обратите внимание на оставшееся время). Итак, все выглядит так, как будто работает правильно.

Я думаю, что одна путаница может быть связана со свойствами NSProgress и их влиянием на значения ключей в dict userInfo. Установка свойств не добавляет значения ключа к пользователю userInfo, а установка значений ключа не устанавливает свойства. Например, установка вида хода выполнения не добавляет NSProgressFileOperationKindKey к диктовке userInfo. Значение в userInfo dict, если оно присутствует, является в большей степени переопределением свойства, которое используется только при создании localizedAdditionalDescription.

Вы также можете увидеть пользовательское значение ключа, которое я добавил. Итак, все это выглядит так, будто все работает правильно. Можете ли вы указать мне на что-то, что все еще отвлекает?

Я хотел прокомментировать ответ @clarus, но ТАК не позволяет мне делать удобочитаемое форматирование в комментарии. TL;DR - их понимание всегда было моим пониманием, и это то, что меня поразило, когда я начал работать с NSProgress несколько лет назад.

Для подобных вещей мне нравится проверять код Swift Foundation на предмет подсказок реализации. Возможно, это не на 100% авторитетно, если что-то еще не сделано, но мне нравится видеть общее мышление.

Если вы посмотрите на реализацию setUserInfoObject(: forKey:) Вы можете видеть, что реализация просто устанавливает пользовательскую информацию dict, не передавая ничего до родителя.

И наоборот, обновления, которые влияют на дочернюю дробь, завершаются явным образом обратным вызовом (private) _parent свойство, указывающее его состояние, должно обновляться в ответ на дочернее изменение.

Это личное _updateChild(: from: to: portion:) только кажется, что обновление завершенной дроби, а не что-либо, связанное с пользовательским информационным словарем

Другие вопросы по тегам