简体   繁体   English

如何使用/测试子NSProgress实例的NSProgress userInfo更改

[英]How to use/test NSProgress userInfo changes of a child NSProgress instance

I'm implementing NSProgress support in a library, and I wrote some unit tests to test that everything's working correctly. 我正在库中实现NSProgress支持,并且编写了一些单元测试来测试一切是否正常运行。 While ideally I'd like to be able to pass some additional metadata ( userInfo keys not used by NSProgress itself, but for users of my API to consume), for now I'm just trying to get localizedDescription and localizedAdditionalDescription to work like the documentation says they should. 理想情况下,我希望能够传递一些其他元数据( NSProgress本身未使用的userInfo键,但供我的API用户使用),现在我只是想让localizedDescriptionlocalizedAdditionalDescription像文档一样工作说他们应该。 Since the method I'm testing extracts files from an archive, I set the kind to NSProgressKindFile and set the various keys associated with file operations (eg NSProgressFileCompletedCountKey ). 由于我正在测试的方法是从档案中提取文件,因此我将kind设置为NSProgressKindFile并设置与文件操作相关的各种键(例如NSProgressFileCompletedCountKey )。

I expect when I observe changes to localizedDescription with KVO, that I'll see updates like this: 我希望当我观察到KVO对localizedDescription更改时,会看到如下更新:

Processing “Test File A.txt” 处理“测试文件A.txt”

Processing “Test File B.jpg” 处理“测试文件B.jpg”

Processing “Test File C.m4a” 处理“测试文件C.m4a”

When I stop at a breakpoint and po the localizedDescription on the worker NSProgress instance ( childProgress below), that is in fact what I see. 当我停在一个断点, polocalizedDescription的工人NSProgress实例( childProgress以下),这其实是在我所看到的。 But when my tests run, all they see is the following, implying it's not seeing any of the userInfo keys I set: 但是,当我运行测试时,他们看到的仅是以下内容,这表明它没有看到我设置的任何userInfo键:

0% completed 0%完成

0% completed 0%完成

53% completed 已完成53%

100% completed 100%完成

100% completed 100%完成

It looks like the userInfo keys I set on a child NSProgress instance are not getting passed on to its parent, even though fractionCompleted does. 看起来我在子NSProgress实例上设置的userInfo键没有传递给它的父实例,即使fractionCompleted确实如此。 Am I doing something wrong? 难道我做错了什么?

I give some abstract code snippets below, but you can also download the commit with these changes from GitHub . 我在下面提供了一些抽象代码段,但是您也可以从GitHub下载具有这些更改的提交。 If you'd like to reproduce this behavior, run the -[ProgressReportingTests testProgressReporting_ExtractFiles_Description] and -[ProgressReportingTests testProgressReporting_ExtractFiles_AdditionalDescription] test cases. 如果您想重现此行为,请运行-[ProgressReportingTests testProgressReporting_ExtractFiles_Description]-[ProgressReportingTests testProgressReporting_ExtractFiles_AdditionalDescription]测试用例。

In my test case class: 在我的测试用例类中:

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

Then, in my class under test: 然后,在我正在测试的班级中:

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

At the time I increment childProgress.completedUnitCount , this is what the userInfo looks like in the debugger. 在我增加childProgress.completedUnitCount ,这是userInfo在调试器中的外观。 The fields I set are all represented: 我设置的字段均表示为:

> po childProgress.userInfo

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

When each KVO notification comes back, this is how notificationProgress.userInfo looks: 当每个KVO通知返回时, notificationProgress.userInfo外观如下:

> po notificationProgress.userInfo

{
}

Ok, I had a chance to look at the code again with more coffee in my system and more time on my hands. 好的,我有机会再次查看代码,因为系统中的咖啡更多,手上的时间也更多。 I'm actually seeing it working. 我实际上看到它正在工作。

In your testProgressReporting_ExtractFiles_AdditionalDescription method, I changed the code to this: 在您的testProgressReporting_ExtractFiles_AdditionalDescription方法中,我将代码更改为:

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

And then in observeValueForKeyPath, I printed these objects: 然后在observeValueForKeyPath中,我打印了这些对象:

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

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

You can see the key-values I added, and the localizedAdditionalDescription was created based on those entries (notice the time remaining). 您可以看到我添加的键值,并且基于这些条目创建了localizedAdditionalDescription(请注意剩余时间)。 So, this all looks like it's working correctly. 因此,这一切看起来都正常工作。

I think one point of confusion might be around the NSProgress properties and their effect on the key-values in the userInfo dict. 我认为,NSProgress属性及其对userInfo dict中的键值的影响可能引起混淆。 Setting the properties doesn't add key-values to the userInfo dict, and setting the key-values doesn't set the properties. 设置属性不会将键值添加到userInfo字典,并且设置键值不会设置属性。 For example, setting the progress kind doesn't add the NSProgressFileOperationKindKey to the userInfo dict. 例如,设置进度类型不会将NSProgressFileOperationKindKey添加到userInfo字典。 The value in the userInfo dict, if present, is more of an override of the property that's only used when creating the localizedAdditionalDescription. userInfo dict中的值(如果存在)更多地是对属性的覆盖,该属性仅在创建localizedAdditionalDescription时使用。

You can also see the custom key-value I added. 您还可以看到我添加的自定义键值。 So, this all looks like it's working right. 因此,这一切似乎都正常运行。 Can you point me to something that still looks off? 你能指出我一些看起来还不错的东西吗?

I wanted to comment on @clarus's answer, but SO won't let me do readable formatting in a comment. 我想对@clarus的答案发表评论,但是SO不会让我在评论中进行可读的格式化。 TL;DR - their take has always been my understanding and it's something that bit me when I started working with NSProgress a few years back. TL; DR-他们的NSProgress一直是我的理解,几年前我开始使用NSProgress时,这让我NSProgress有些NSProgress

For stuff like this, I like to check the Swift Foundation code for implementation hints. 对于这样的事情,我想检查Swift Foundation代码中的实现提示。 It's maybe not 100% authoritative if stuff's not done yet, but I like seeing the general thinking. 如果还没有完成,这可能不是100%权威,但是我喜欢看到一般的想法。

If you look at the implementation of setUserInfoObject(: forKey:) , you can see that the implementation simply sets the user info dict without propagating anything up to the parent. 如果看一下setUserInfoObject(: forKey:)的实现,可以看到该实现只是设置了用户信息字典,而没有向父对象传播任何东西。

Conversely, updates that impact the child's fraction completed explicitly call back to the (private) _parent property to indicate its state should update in response to a child change. 相反,影响子级分数的更新已完成, 显式地调用 (private) _parent属性,以指示其状态应响应子级更改而更新。

That private _updateChild(: from: to: portion:) only seems concerned updating the fraction completed and not anything related to the user info dictionary. 私有的_updateChild(: from: to: portion:)似乎只在关注更新已完成的部分,而与用户信息字典无关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM