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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution