nsprogress windowThe purpose of this article is to comment upon the recommended guidelines for the use of NSProgress. It does not cover the details of the NSProgress class as this is adequately covered elsewhere. It is assumed you are familiar with NSProgress and its purpose.

For a good introduction to NSProgress you should watch the Apple WWDC 2015 Session Best Practices for Progress Reporting which covers both NSProgress, NSProgressReporting and in particular their implementation and recommended usage. It is about its usage where I feel there is an important omission although it is somewhat implicit in the Apple WWDC session.

Problems with NSProgress

I came across an issue when implementing NSProgress in my SKNZipArchiver framework. In particular with using child and parent progresses. I wanted to have a parent NSProgress in the SKNZipArchive object. The SKNZipArchive contains a set (two actually) of SKNZipFile objects, each of which would have a child NSProgress. Originally the idea was that both types of object, SKNZipArchive and SKNZipFile, would adopt NSProgressReporting.

This led to a problem; once an NSProgress has been made a child of a parent progress there is no way to reuse it. The clue is in the WWDC session where towards the end he states:

“NSProgress objects cannot be reused. Once they’re done, they’re done. Once they’re cancelled, they’re cancelled. If you need to reuse an NSProgress, instead make a new instance and provide a mechanism so the client of your progress knows that the object has been replaced, like a notification.”

I’ve not read that in any of the Apple documentation or in any other articles on NSProgress. This leads to a problem if you have a persistent / static object that hangs around after an operation which reports a progress has finished. What should you do with the progress property? Should it be set to nil? When should it be set to nil? How does that affect the progress of the parent object? Moreover, what if your objects allow concurrent asynchronous operations? Frankly, the suggestion of using some form of notification sounds horrid and thoughts of coupling spring to mind.

It becomes clear that it is not meaningful for static objects to adopt NSProgressReporting in the first place. In the WWDC session’s demo, the progress for downloading a photo was implemented as a property of the PhotoDownload class. I would argue this is good practice, although no mention was made of it.

Moreover, just what is the progress of an NSImage or UIImage? Images do not have an intrinsic progress. In the same way neither do my SKNZipArchive or SKNZipFile objects. It is not these objects that have a progress but rather the operations / tasks that operate upon them.

One solution, and I have seen it implemented, is to have a method on an object that returns an NSProgress. For example:  -(NSProgress *)doSomeTask

The problems with returning a progress from a method:

  • Your object no longer adopts NSProgressReporting. The progress comes from the method and is not a property of the object.
  • There is the issue of KVO on the NSProgress properties; fractionCompleted, localizedDescription and localizedAdditionalDescription. If you’re going to be returning a progress from a method then that method needs to be asynchronous and return immediately. But that would mean the progress is possibly already started before you’ve had a chance to set up the KVO.
  • When do you tear down the KVO on the progress? You will need to compare the completedUnitCount with the totalUnitCount each time the fractionCompleted changes and make sure you tear down the KVO before the progress is deallocated.
  • You might need to retain the progress to avoid error messages about objects deallocating with KVO observers.
  • It’s ugly and doesn’t make sense.

Step in NSOperation…

This is a perfect example of an object that is suitable to adopt NSProgressReporting.

  • The NSProgress is one shot – no need to reuse it.
  • The NSProgress is initialised with the operation object and is therefore a property on the operation object, i.e. it properly adopts NSProgressReporting.
  • After initialising the operation you can set up the necessary KVO before starting the operation.
  • When the operation finishes you know it’s time to tear down the KVO.
  • It’s easy to have multiple operations acting on the same object, possibly concurrently, each with their own progress. Each progress could easily form part of a progress hierarchy.

So the important lesson here is that it is operation / task objects that should adopt NSProgressReporting and not the objects on which the task operates.

Again from the WWDC session:

“NSProgress objects can be a conduit for cancellation. The creator of the NSProgress sets cancelable and the cancellationHandler.”

This is trivial to implement if cancelling is supported in your NSOperation object. Pausing and resuming might be a bit more work but they are optional.

It’s not hard to implement a subclass of NSOperation and add the NSProgress property. You can even have a convenience method on your static object that returns the operation object. In fact this is exactly the approach I have taken for my SKNZipArchive framework.

Examples of Apple’s implementation

The WWDC presentation refers to NSBundleResourceRequest, UIDocument and NSData.

I can find no support for NSProgressReporting in NSData. Perhaps this was the intention at WWDC but later dropped.

The NSBundleResourceRequest certainly makes sense. The object is instantiated, you setup KVO on the progress, then call -beginAccessingResourcesWithCompletionHandler:

However, the implementation in UIDocument is dependant on the property documentState having the UIDocumentStateProgressAvailable option set. The documentation states;

“The document is being downloaded or uploaded and progress information is available. When this state is set, you can use the progress property of the document to monitor the current operation.”

This sounds a bit iffy to me but I have never used it in earnest so perhaps it works OK in practice.

Two classes that would seem to benefit from NSProgressReporting would be NSOperation and NSURLSessionTask. Perhaps we will see this implemented in the future.

NSProgress and NSProgressReporting