WWDC 2015

Best Practices for Progress Reporting

Progress indicators are seen everywhere in any systems such as loading webpages of browsers, installation of applications and reminding time and tasks for user completion. NSProgress is class to accurate report the progress of work.

Documentation of NSProgress

class NSProgress {
    var totalUnitCount: Int64 //total work units 
    var completedUnitCount: Int64 //total work units completed
    var fractionCompleted: Double { get } //computed property 
    var indeterminate: Bool { get } // return true if totalUnitCount < 0 or completedUnitCount < 0, useful when the total work unit is unknown
    var localizedDescription: String!
    var localizedAdditionalDescription: String!
    var kind: NSProgressKind
}

Localization

currently only setting kind as NSProgressKindFile to change the localizedDescriptions and localizedAdditionalDescription

Responsibilities

For Creator

  • updating totalUnitCount
  • kind
  • userInfo for show different descriptions
  • completedUnitCount

For Clients

  • Do not set properties of NSProgress
  • get totalUnitCount
  • get completedUnitCount
  • get gractionCompleted
  • get localizedDescriptions

Composition

Composition of progress is needed when not tracking single progress but many progress

Use a parent progress that allows childs' progress to report back via pendingUnitCount

Implicit Composition

In older system, implicit composition is used

let parentProgress = NSProgress()
parentProgress.totalUnitCount = 2
//assigning 1 unit count to child's progress to finish
parentProgress.becomeCurrentWithPendingUnitcount(1)
startDownload() //NSProgress(totalUnitCount:...)
parentProgress.resignCurrent() //completed child's progress
  • create with NSProgress once the child's work begins
  • Document it to tell others to use the child's work progress
  • resignCurrent mark pendingUnitCount as finished
  • parent's completedUnitCount is updated

Explicit

let childProgress = child.progress
let parentProgress = ...
parentProgress.addChild(childProgress, withPendingUnitCount:1)
  • use explicit when there are methods returning the progress
  • system version >= iOS 9 or OSX 10.11

Cancel, Pausing and resuming

Cancel

var cancellable: Bool
var cancellationHandler: (() -> Void)?
var cancelled: Bool { get }

calling progress.cancel() will

  • set cancelled to true
  • invoke cancellationHandler
  • cancel actions also pass along to children progress
  • It’s permanent

Pausing and resuming

var pausable: Bool
var pausingHandler: (() -> Void)?
var resumingHandler: (() -> Void)?
var paused: Bool { get }

calling progress.pause() or progress.resume() will

  • Sets paused to true/false
  • Invokes the pausingHandler/resumingHandler
  • Pausing/resuming flows down to children

User Interface

NSProgress properties are key value observable

  • Add KVO observers to update your UI
  • Not necessarily called on main thread
//somewhere in code
progress.addObserver(self, forKeyPath: "fractionCompleted", options: [], context: &observationContext)


override func observeValueForKeyPath(keyPath: ..., object: ..., ..., context: ...) {
    if context == &observationContext && keyPath == "fractionCompleted" {
        NSOperationQueue.mainQueue().addOperationWithBlock {
            let progress = (object as! NSProgress)
            progressView.progress = Float(progress.fractionCompleted)
        }
    }
    else {
        super.observeValueForKeyPath(...)
    }     
}

Best Practices

  • Dont use fractionCompleted to determine completion
    • It is a float
    • Use completedUnitCount >= totalUnitCount (unless indetermineate or zero)
  • NSProgress can not be reused
    • make a new instance if additional progress
  • Dont update completedUnitCount in a tightLoop
  • Dont forget update to 100%