1

我正在NSProgress一个库中实现支持,并且我编写了一些单元测试来测试一切是否正常工作。虽然理想情况下我希望能够传递一些额外的元数据(userInfo密钥本身不使用NSProgress,但供我的 API 的用户使用),但现在我只是想像文档所说的那样获取localizedDescriptionlocalizedAdditionalDescription工作。由于我正在测试的方法是从存档中提取文件,因此我设置了kindtoNSProgressKindFile并设置了与文件操作相关的各种键(例如NSProgressFileCompletedCountKey)。

我希望当我观察到localizedDescriptionKVO 的变化时,我会看到这样的更新:

处理“测试文件 A.txt”</p>

处理“测试文件 B.jpg”</p>

处理“测试文件 C.m4a”</p>

当我停在断点和工作实例上(po如下)时,这实际上就是我所看到的。但是当我的测试运行时,他们看到的只是以下内容,这意味着它没有看到我设置的任何键:localizedDescriptionNSProgresschildProgressuserInfo

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

在我 incrementchildProgress.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

{
}
4

2 回答 2

4

我想对@clarus 的回答发表评论,但不允许我在评论中进行可读格式设置。TL;DR - 他们的看法一直是我的理解,当我NSProgress几年前开始工作时,这让我很痛苦。

对于这样的东西,我喜欢检查Swift Foundation代码以获取实现提示。如果事情还没有完成,它可能不是 100% 权威,但我喜欢看到一般的想法。

如果您查看 的实现setUserInfoObject(: forKey:),您会发现该实现只是设置了用户信息字典,而没有将任何内容传播到父级。

相反,影响孩子分数的更新显式回调(私有)_parent属性以指示其状态应更新以响应孩子的变化。

该私人_updateChild(: from: to: portion:)似乎只涉及更新已完成的分数,而不涉及与用户信息字典相关的任何内容。

于 2017-09-22T20:23:09.067 回答
3

好的,我有机会再次查看代码,系统中有更多的咖啡和更多的时间。我实际上看到它在工作。

在您的 testProgressReporting_ExtractFiles_AdditionalDescription 方法中,我将代码更改为:

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

然后在 observeValueForKeyPath 中,我打印了这些对象:

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

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

您可以看到我添加的键值对,并且根据这些条目创建了本地化附加描述(注意剩余时间)。所以,这一切看起来都正常工作。

我认为混淆的一点可能是 NSProgress 属性及其对 userInfo 字典中键值的影响。设置属性不会将键值添加到 userInfo 字典,设置键值不会设置属性。例如,设置进度类型不会将 NSProgressFileOperationKindKey 添加到 userInfo 字典中。userInfo dict 中的值(如果存在)更多地是对仅在创建本地化AdditionalDescription 时使用的属性的覆盖。

您还可以看到我添加的自定义键值。所以,这一切看起来都正常。你能指出我仍然看起来不正常的东西吗?

于 2017-09-21T15:19:10.807 回答