【问题标题】:How to use/test NSProgress userInfo changes of a child NSProgress instance如何使用/测试子 NSProgress 实例的 NSProgress userInfo 更改
【发布时间】:2018-02-28 08:14:18
【问题描述】:

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

我希望当我观察到使用 KVO 对 localizedDescription 的更改时,我会看到如下更新:

处理“测试文件 A.txt”

处理“测试文件 B.jpg”

处理“测试文件 C.m4a”

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

0% 完成

0% 完成

53% 完成

100% 完成

100% 完成

看起来我在子 NSProgress 实例上设置的 userInfo 键没有传递给它的父实例,尽管 fractionCompleted 确实如此。我做错了吗?

我在下面给出了一些抽象代码sn-ps,但您也可以下载带有这些更改的提交from 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

{
}

【问题讨论】:

  • 你能显示更多的上下文代码吗?调用 setUserInfoObject 后控制台中的 po myCustomObject、po MyCustomKey 和 po [childProgress userInfo] 立即显示什么?
  • @clarus 我添加了po 输出。对于上下文,您还需要哪些其他代码?与NSProgress 相关的内容不多。 fractionCompleted 得到准确报告。
  • observeValueForKeyPath:ofObject:change:context: 中的代码是什么样的?而且,您在上面使用 parentProgress 的地方,是否与原始 childProgress 对象引用相同?
  • @clarus 感谢您的建议。我更新了更详尽的代码示例。它应该有望解决您的问题。
  • @clarus 随着我对此进行了更多的研究,看起来甚至记录在案的userInfo 键也没有按预期工作。我已经更新了问题,并包含了一个指向 GitHub 提交的链接,您可以下载并运行。

标签: ios objective-c macos cocoa nsprogress


【解决方案1】:

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

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

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

相反,影响子级分数的更新已完成explicitly call back 到(私有)_parent 属性,以指示其状态应更新以响应子级更改。

那个私人_updateChild(: from: to: portion:) 似乎只关心更新完成的分数,而不是任何与用户信息字典相关的东西。

【讨论】:

  • 谢谢,很有帮助
【解决方案2】:

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

在您的 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

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

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

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

【讨论】:

  • 这很有趣。我没想到更深入地了解父母的进步。不过,我确实认为它应该按照我的意图工作。层次结构深深植根于 NSProgress 的运作方式。感谢您抽出宝贵时间查看。
  • 在仔细查看后更新了文本,删除了我之前的 cmets,因为他们没有添加任何内容。
  • 感谢您的坚持!我很感激。我认为做出这种改变的不足之处在于URKArchive 类的工作人员NSProgress 中的更新仍然没有传播。当我在URKArchive 中的progress 对象上po 时,它总是显示我设置的所有键。这有意义吗?
  • 那么,当您说“向上传播”时,您是否期望子进程的 userInfo dicts 将被合并到父进程中?因为我没有看到任何表明会发生这种情况的信息,并且我希望 userInfo dict 特定于特定对象。否则,您可能会在他们没有预料到的情况下覆盖另一个孩子的钥匙,甚至是父母的钥匙。如果您希望将某些内容暴露给另一个进度对象,例如父级,那么我认为您需要将这些值添加到父级的 userInfo 中,或者以其他方式使它们在子对象之外可用。
  • 父母只是在跟踪整体进度,所以我不明白为什么孩子可能会覆盖父母的键来格式化本地化字符串——我认为父母会期望使用自己的。是否有文档或示例表明会发生这种情况,还是我误解了这个问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-11
相关资源
最近更新 更多