I'm implementing NSProgress
support in a library, and I wrote some unit tests to test that everything's working correctly. 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. 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 (e.g. NSProgressFileCompletedCountKey
).
I expect when I observe changes to localizedDescription
with KVO, that I'll see updates like this:
Processing “Test File A.txt”
Processing “Test File B.jpg”
Processing “Test File 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. But when my tests run, all they see is the following, implying it's not seeing any of the userInfo
keys I set:
0% completed
0% completed
53% completed
100% completed
100% completed
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. Am I doing something wrong?
I give some abstract code snippets below, but you can also download the commit with these changes from GitHub. If you'd like to reproduce this behavior, run the -[ProgressReportingTests testProgressReporting_ExtractFiles_Description]
and -[ProgressReportingTests testProgressReporting_ExtractFiles_AdditionalDescription]
test cases.
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. 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:
> po notificationProgress.userInfo
{
}