我在库中实现NSProgress
支持,并且我编写了一些单元测试来测试所有内容是否正常工作。理想情况下,我希望能够传递一些额外的元数据(userInfo
本身未使用的NSProgress
密钥,但我的API用户需要使用这些密钥,目前我还是只是试图让localizedDescription
和localizedAdditionalDescription
像文档所说的那样工作。由于我测试的方法从存档中提取文件,因此我将kind
设置为NSProgressKindFile
并设置与文件操作相关的各种键(例如NSProgressFileCompletedCountKey
)。
我希望当我观察到使用KVO对localizedDescription
进行更改时,我会看到这样的更新:
处理“测试文件A.txt”
处理“Test File B.jpg”
处理“测试文件C.m4a”
当我停在断点处且po
工作人员localizedDescription
上的NSProgress
(childProgress
以下)时,这实际上就是我所看到的。但是当我的测试运行时,他们看到的只是以下内容,这意味着它没有看到我设置的任何userInfo
键:
0%完成了
0%完成了
53%完成了
100%完成
100%完成
看起来我在子userInfo
实例上设置的NSProgress
键没有传递给它的父级,即使fractionCompleted
也是如此。我做错了吗?
我在下面提供一些抽象的代码片段,但您也可以使用这些更改下载提交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
{
}
答案 0 :(得分: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
您可以看到我添加的键值,并根据这些条目创建localizedAdditionalDescription(注意剩余时间)。所以,这一切看起来都是正常的。
我认为混淆的一点可能在于NSProgress属性及其对userInfo dict中键值的影响。设置属性不会将键值添加到userInfo dict,并且设置键值不会设置属性。例如,设置进度类型不会将NSProgressFileOperationKindKey添加到userInfo dict。 userInfo dict中的值(如果存在)更多地是对创建localizedAdditionalDescription时仅使用的属性的覆盖。
您还可以看到我添加的自定义键值。所以,这一切看起来都是正常的。你能指出一些看起来还不错的东西吗?
答案 1 :(得分:3)
我想对@ clarus的回答发表评论,但是我不会让我在评论中做可读格式。 TL; DR - 他们的观点一直是我的理解,当我几年前开始与NSProgress
合作时,这就是我的事。
对于这样的东西,我想检查Swift Foundation代码的实现提示。如果事情尚未完成,那可能不是100%权威,但我喜欢看到一般思维。
如果你看一下setUserInfoObject(: forKey:)
的实现,你可以看到实现只是设置了用户信息dict,而没有将任何东西传播到父代。
相反,影响子级分数的更新已完成explicitly call back到(私有)_parent
属性,以指示其状态应更新以响应子级更改。
私有_updateChild(: from: to: portion:)
似乎只关注更新已完成的分数而不是与用户信息词典相关的任何内容。