Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With NSProgress, are the completion handlers intended for the UI or the download controller?

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)

like image 687
Christian Heidarson Avatar asked Sep 23 '13 12:09

Christian Heidarson


1 Answers

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.

like image 196
jamesmoschou Avatar answered Oct 17 '22 06:10

jamesmoschou