NSProgress with Asynchronous Tasks

Keith Smiley

NSProgress was introduced in iOS 7 and OS X 10.9 with the self-proclaimed purpose of being a loosely coupled progress reporting mechanism. In practice this means that intermediary code doesn’t need to know anything about the progress being tracked between the user interface layer where your application is doing work.

Let’s see how this works in practice. In our view controller we can setup something like this:

self.progress = [NSProgress progressWithTotalUnitCount:-1];
[self.progress becomeCurrentWithPendingUnitCount:1];
[APIClient doSomethingWithCompletion:^{
  // Asynchronous activity is done
}];

[self.progress resignCurrent];

Here we are creating a new NSProgress instance on the main thread. Setting the totalUnitCount to -1 means our progress starts with an indeterminate state. In our UI we would reflect this state with a UIActivityIndicatorView on iOS or a NSProgressIndicator with indeterminate set to YES on OS X.

Then we tell our NSProgress to become the current progress. Under the hood this registers our newly created progress as the +currentProgress for this thread. This becomes important when we want to create child NSProgress instances elsewhere in your code which will affect this main progress. Now we can perform some asynchronous work, and resign from the current progress. Resigning the progress restores the previous NSProgress instance where becomeCurrentWithPendingUnitCount: was called. This allows us to chain multiple progress objects that are all doing work on the same thread.

Now, in our doSomethingWithCompletion: implementation, no matter how many levels deep it is, we can grab the NSProgress object we just created and build off of it without explicitly passing it as an argument. Let’s look at how this may work:

- (void)doSomethingWithCompletion:(dispatch_block_t)completionBlock
{
    // Our progress instance from above
    NSProgress *mainProgress = [NSProgress currentProgress];
    // A new child progress
    NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1];
    [self getObjectIDsWithCompletion:^(NSArray *IDs) {
        dispatch_async(dispatch_get_main_queue(), ^{
            mainProgress.totalUnitCount = 1;
            progress.totalUnitCount = IDs.count;
        });

        [self getEachObjectFromIDs:IDs
                         withBlock:^(NSDictionary *object) {
                            [self processObject:object];

                            dispatch_async(dispatch_get_main_queue(), ^{
                                progress.completedUnitCount++;
                            });
                      } completion:^{
                            dispatch_async(dispatch_get_main_queue(), ^{
                                progress.completedUnitCount = progress.totalUnitCount;
                                completionBlock();
                            });
                      }];
    }];
}

There is a lot going on here so let’s step through it. We first grab the mainProgress we created in our UI layer. We only need to do this since, in this example, our progress starts off indeterminate until our first network request has completed. We then create a new child progress that is also indeterminate to start. Under the hood this is calling -initWithParent:userInfo: on NSProgress and passing the +currentProgress which happens to be our mainProgress from the UI layer. Because of the loose coupling here, if the UI layer wasn’t utilizing this NSProgress chain, our networking code could look exactly the same. The only difference is no one would be observing the changes that are happening.

Next we fire off our first asynchronous call which gets us an array of IDs of objects that we need to fetch. Now that we know how many requests we are going to perform our NSProgress instances no longer need to be indeterminate. Our mainProgress gets a totalUnitCount of 1, since the only unit of work that needs to be completed is that being completed by our single child progress. As for the child progress, it gets a totalUnitCount matching the number of requests we need to perform. Each time one of these requests finishes, we increment the completedUnitCount which is automatically reflected by our mainProgress. We will look at binding this to our UI momentarily. Finally, we call our completion block which brings us back up to the UI layer.

At this point we have a fully functional NSProgress chain that we can observe changes on and reflect in our UI.

We have many options on how to bind our NSProgress instance to our UI. On OS X we could use Cocoa Bindings. Otherwise we could use KVO, possible with a wrapper or ReactiveCocoa. On OS X we can initialize our NSProgressIndicator with a minValue and maxValue of 0.0 and 1.0 respectively. This will match the behavior of UIProgressView so that we can bind either value on NSProgressIndicator or progress on UIProgressView to the fractionCompleted on NSProgress. The fractionCompleted property uses all child progress instances to compute an overall value for the fraction that is completed between instances. This makes for a really accurate representation of overall progress for multiple operations.

After setting this up we now have an awesome looking and more informative progress indicator. Here is the indicator we’ve created:

Progress Animation