I’ve been working on a replacement for Progress (NSProgress) for use in my own 
code for a while now. It hasn’t been extensively battle-tested yet, but for the 
time being it seems to work. The full code for it is at 
https://github.com/CharlesJS/CSProgress 
<https://github.com/CharlesJS/CSProgress> (BSD licensed, with the exception 
that Apple/the Swift team is granted permission to treat it as public-domain 
code if they so choose).

A rather detailed description of this class and its motivations are in the 
read-me for the linked repository, and copying the whole thing would probably 
run afoul of the mailing list’s size limit. However, these are the highlights:

- Much better performance. In my testing, updating the completedUnitProperty, 
with something observing it, takes 1.04 seconds on my 2013 Retinabook, compared 
with NSProgress’s 52.91 seconds (58.53 seconds if an autorelease pool is used). 
This frees back-end code from the responsibility of being adulterated with what 
is effectively UI code in order to determine when the progress object should be 
updated.

- Much better RAM usage. NSProgress generates a large number of autoreleased 
objects, which can cause considerable memory bloat if they are not wrapped in 
an autorelease pool, which further hinders performance. By using Swift-native 
types whenever available, this problem is avoided.

- Better thread safety, by using atomic operations to update the 
completedUnitCount property. NSProgress requires taking the lock twice, once to 
read the old value, and once to write the new value. CSProgress reduces this by 
allowing the delta to be sent, meanwhile solving the race condition that can 
occur if something else updates the property in between the two operations.

- Includes a “granularity” property, customizable by the user, which determines 
how often fractionCompleted notifications will be sent. The default is 0.01, 
which means that fractionCompleted notifications will be sent whenever 
fractionCompleted becomes at least 0.01 greater than its value at the time the 
last notification was sent. For a steady increase from 0 to totalUnitCount, the 
notification will be sent 100 times. This prevents unnecessary UI updates when 
the progress is incremented by a negligible amount.

- Much better and “Swiftier” interface:
        - All observations are done via closures, rather than KVO.
        
        - All observations are sent via an OperationQueue which is specifiable 
by the user, and which defaults to the main queue. This means that
          UI elements can be directly set from within the notification closures.

        - Unlike the closure-based APIs on NSProgress such as 
cancellationHandler, CSProgress allows multiple closures to be specified for 
each observation type, in case a progress object has more than one client that 
wants to observe it.

        - Uses generics for all functions taking unit counts, so that any 
integer can be passed to them instead of having to cast everything to Int64.

        - Adds a wrapper struct encapsulating both a parent progress and a 
pending unit count, so that explicit tree composition can be used without
          requiring functions to know the pending unit counts of their parents, 
thus preserving the loose coupling inherent to the old-fashioned implicit style.

- Bridging to the standard Objective-C Progress/NSProgress. This code was added 
the most recently, and is the most hackish by far, so it will need a lot of 
testing, but for the time being it seems to pass my unit tests. The bridging is 
designed to allow CSProgress to insert itself into any part of an NSProgress 
tree, being able to handle NSProgress objects as parents or as children. 
Updates to parent NSProgresses are also done on a user-specified 
OperationQueue, which defaults to the main queue. This means that if you prefer 
NSProgress’s KVO-based API, you can actually bind your UI elements directly to 
the NSProgress, and since all updates to the NSProgress will happen on the main 
queue, you won’t need any special property observers to forward notifications 
to the main thread.

I am wondering what the community thinks of this. I’ve tried to keep the 
interface as close to Progress/NSProgress’s as possible, to allow this to be a 
drop-in replacement. There are a few features of NSProgress that are still 
unimplemented; however, I could add them if the response is positive. With the 
interface mirroring Progress’s, this means that, with some tweaking and 
extensive battle testing, CSProgress could potentially serve as a replacement 
for Progress, with the original Progress being renamed back to NSProgress. The 
result would break binary compatibility, but could be source-compatible, and I 
think it has the potential to improve the experience on Swift.

What do you think?

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to