This site uses cookies to deliver our services and to show you relevant ads and job listings.
By using our site, you acknowledge that you have read and understand our Cookie Policy, Privacy Policy, and our Terms of Service.
Your use of Stack Overflow’s Products and Services, including the Stack Overflow Network, is subject to these policies and terms.

Join us in building a kind, collaborative learning community via our updated
Code of Conduct.

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.

Can you show some more code for context? What do po myCustomObject, po MyCustomKey, and po [childProgress userInfo] in the console show immediately after calling setUserInfoObject?
– clarusSep 19 '17 at 14:56

@clarus I added the po output. What other code would you need for context? There isn't much else as it pertains to NSProgress. The fractionCompleted is getting reported accurately.
– DovSep 19 '17 at 20:41

What does the code in observeValueForKeyPath:ofObject:change:context: look like? And, where you use parentProgress above, is that the same as the original childProgress object reference?
– clarusSep 19 '17 at 21:04

@clarus Thanks for the suggestion. I updated with more thorough code examples. It should hopefully clear up your question.
– DovSep 20 '17 at 12:22

@clarus As I've worked on this more, it looks like even documented userInfo keys aren't working as expected. I've updated the question, and included a link to a GitHub commit you could download and run.
– DovSep 21 '17 at 14:15

You can see the key-values I added, and the localizedAdditionalDescription was created based on those entries (notice the time remaining). 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. Setting the properties doesn't add key-values to the userInfo dict, and setting the key-values doesn't set the properties. For example, setting the progress kind doesn't add the NSProgressFileOperationKindKey to the userInfo dict. The value in the userInfo dict, if present, is more of an override of the property that's only used when creating the 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?

That's interesting. I didn't think to look deeper into the parent progress. I do think that it's supposed to work the way I'm intending, though. Hierarchy is deeply ingrained into the way NSProgress is meant to function. Thanks for taking the time to check it out.
– DovSep 21 '17 at 15:23

updated the text after looking at it more closely, removed my comments from earlier because they didn't add anything.
– clarusSep 22 '17 at 0:01

thanks for sticking with this! I appreciate it. I think where making that change falls short is that the updates in the URKArchive class's worker NSProgress still don't propagate up. When I po on the progress object in URKArchive, it always did show all keys I set. Does that make sense?
– DovSep 22 '17 at 0:18

So, when you say "propagate up," is it your expectation that the child processes' userInfo dicts are going to be merged into the parent's? Because I don't see anything that indicates that would happen, and I'd expect the userInfo dict to be specific to a particular object. Otherwise, you might overwrite another child's keys or even the parent's without them expecting it. If you want something exposed to another progress object, say the parent, then I think you need to add those values to the parent's userInfo, or otherwise make them available outside of the child object.
– clarusSep 22 '17 at 1:14

The parent is just keeping track of overall progress, so I can't see why it would be desirable for a child to potentially overwrite the parent's keys for formatting the localization string--I'd think the parent would expect to use its own. Is there documentation or an example somewhere that suggested that would happen, or am I misunderstanding the issue?
– clarusSep 22 '17 at 1:14

I wanted to comment on @clarus's answer, but SO won't let me do readable formatting in a comment. 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.

For stuff like this, I like to check the Swift Foundation code for implementation hints. It's maybe not 100% authoritative if stuff's not done yet, but I like seeing the general thinking.

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.

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.