I am using NSProgress to communicate the progress of file downloads in my iOS app. It is a very general-purpose class and I'm a bit scared of its inherent power, especially with the two completion handling properties. There is one for dealing with canceling, and one for pausing (but none for completion, which is perhaps a hit…)
What are these handlers intended for? The code that does the download could put logic in these to deal with user-originated cancels and pauses. However, there is nothing stopping a client to overwrite the handlers with UI code.
So, is it intended for the UI? I am not sure how this is a helpful patterns, since the UI would be originating the cancel or pause anyway. Also, if you are using the progress object to simultaneously present progress across multiple UI elements (the way it is used in MacOS), the different UI elements would potentially all want its own completion handler.
Using the handlers to communicate user actions back to the download controller seems the most useful pattern, but then I would have expected the handler to be set up at initialization and then remain read-only.
What am I missing here?
(P.S. For now I am simply not going to use those handlers and rely on KVO. However, I have an itchy feeling that I am missing out on some fundamental idea behind the class)
I believe the key that you are missing is that the NSProgress
class is designed to be used as a tree of progress objects. Further this tree is created implicitly with child progress objects not needing to be aware that they are attached to a parent, this is where it's real power comes from.
I found the OS X Foundation Release Notes much more helpful than the class reference for NSProgress
:
https://developer.apple.com/library/Mac/releasenotes/Foundation/RN-Foundation/index.html
The reason why it seems handlers can be used for both UI controller logic and data controller logic is that when you construct a parent-child hierarchy, you have two sets of handlers that can be used for both. The parent's handlers would be set at the UI controller level (the 'consumer' of the progress), and the child's handlers would be set by the data controller (the 'provider').
Since the relationship can be created implicitly using becomeCurrentWithPendingUnitCount:
the child progress object will be insulated from parent, which would alleviate your concern about clients overwriting any data-level handlers with their own.
Calling pause
or cancel
on a progress object will propagate that call down the tree, calling any handlers on the way.
An example:
// UI controller level, probably a UIViewController subclass.
- (void)handleDoSomethingButtonTapped:(UIButton *)sender
{
self.progressThatWeObserve =
[NSProgress progressWithTotalUnitCount:100]; // 100 is arbitrary
self.progressThatWeObserve.pausingHandler = ^{
// Update UI, reflect paused state ...
};
[self.progressThatWeObserve becomeCurrentWithPendingUnitCount:100];
[self.dataController doSomethingInBackgroundWithCompletionHandler:^{
// Update UI, remove from view ...
}];
[self.progressThatWeObserve resignCurrent];
}
// Data controller level, a SomethingManager class maybe.
- (void)doSomethingInBackgroundWithCompletionHandler:(void (^)(void)completionHandler
{
self.progressThatWeManipulate =
[NSProgress progressWithTotalUnitCount:289234]; // e.g. bytes to upload
self.progressThatWeManipulate.pausingHandler = ^{
// Actually suspend the network operation ...
};
dispath_async(self.workerQueue, ^{
// Periodically update progress
});
}
Please note I haven't actually done any of this, it is all theory from reading documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With