Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[Applause]
>> VINCE SPADER: Hello.
My name is Vince Spader.
I'm an engineer on
the Cocoa frameworks.
And today I'm going
to be talking to you
about Best Practices
for Progress Reporting,
which in Cocoa means NSProgress.
So I'm going to give you an
introduction in NSProgress,
then we'll talk about composing
NSProgress objects together,
and then how to use
NSProgress as an interface
for cancellation,
pausing, and resuming.
Then we'll talk a little
bit about hooking NSProgress
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Then we'll talk a little
bit about hooking NSProgress
up to your user interface,
and we'll wrap
up with some tips
and best practices.
So let's get started.
NSProgress is an
object in Foundation
that represents the
completion of some work.
That work could be downloading
a file, installing an app,
or something your own
application is doing.
The NSProgress object exists to
let you easily report progress
in your application
across various components,
both yours and the system's.
In fact, several Cocoa APIs are
reporting their progress via
NSProgress, like
NSBundle Resource Request,
UIDocument, and NSData.
So you can use NSProgress
to get information
about what those APIs are
doing in your application.
And NSProgress is localized.
You can use it to show
information to the user
about what's happening.
And we have ways to
influence what it says,
which we'll get to soon.
But first, these are the core
properties on NSProgress.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But first, these are the core
properties on NSProgress.
We have the totalUnitCount,
which is how much work there is
to do, and the
completedUnitCount,
which is how much work
has been completed.
And that gets updated
as the work occurs.
And the fractionCompleted is a
double that gets updated from 0
to 1, letting you know how
far along the work is toward
being completed.
So, totalUnitCount and
completedUnitCount,
the unit these properties are
referring to is up to you.
It is whatever unit
it makes sense
to track in terms of progress.
Perhaps bytes, number of files,
photos, or even abstract units
like percentage points
or fractions of work.
Each individual NSProgress
object has its own idea
of what unit it is reporting in.
And if you don't know how
much work there is total,
so you don't know
your totalUnitCount,
you can make your
progress indeterminant.
And you do that by either
setting the completedUnitCount
or the totalUnitCount
to a negative value.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
or the totalUnitCount
to a negative value.
Next, let's talk
about localization.
NSProgress has two properties,
localized Description
and localized Additional
Description,
that you can display
to the user.
You can set them yourself
but NSProgress will also come
up with something
for you by default.
So, here is an example of the
default localized Description
and localized Additional
Description.
We have an NSProgress with a
totalUnitCount of 5 million
and some, and a
completedUnitCount
of 419 thousand and change.
The default localized
Description is 7 percent
completed, and the localized
Additional Description formats
those numbers nicely.
So this is the default.
And if you want something
different,
you could set the
localized Description
and localized Additional
Description yourself
but then you would need
to actually localize it
in the languages
your app supports.
And instead of doing that,
we have a couple of knobs
that you can use to
alter those defaults.
So the first is through
the kind property.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So the first is through
the kind property.
Currently the only option is
for files, NSProgressKindFile.
Using this means
your units are bytes,
so when NSProgress knows
your units are bytes,
it can format them as such,
so you see now it can say 419
kilobytes of 5.3 megabytes.
The other knob for changing the
default localized Descriptions
is through certain keys in
the user info dictionary.
So NSProgress has a user info
dictionary, there is a method,
Set User Info Object For
Key, that lets you set a key
and value in the userInfo.
And one key that's useful
for virtually any kind
of NSProgress is NSProgress
Estimated Time Remaining Key.
The value there is an NSNumber
of how many seconds
are remaining
until the work is completed.
And you can see if we set
that to, say, 97 seconds,
our localized Additional
Description now includes
that information formatted as
1 minute, 37 seconds remaining.
And there are additional
userInfo keys that are only used
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And there are additional
userInfo keys that are only used
if your kind is set to file.
First let's look at NSProgress
File Operation Kind Key.
This tells NSProgress
the type of operation
that is being performed
on the file.
The values are either
downloading, decompressing,
receiving, or copying, so if
you set the File Operation Kind
to NSProgress File
Operation Kind Downloading,
that updates the
localized Description
to say it is downloading files.
Another key available when your
Kind is File is the NSProgress
File URL Key.
And that is an NSURL of
the file being worked on.
So when you set that,
the localized Description will
include the name of the file,
in this example, photos.zip,
which is from the given URL.
There are also options
for if you're operating
on a set of files.
NSProgress File Total Count
Key and Completed Count Key.
So, here's an example
where we're setting the
File Completed Count to 7,
and the File Total Count to 9,
and the localized Description
might use that information,
so now it says, downloading
9 files.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
so now it says, downloading
9 files.
Note that the kind is still File
and the units are still bytes,
it is just the total bytes
of the files being worked on.
And finally, we have the
NSProgress Throughput Key.
And that's the bytes-per-second
that the file operations
are being performed at.
So, say, it is downloading
at a blazing fast
50,000 bytes-per-second,
if we set the throughput
on the userInfo,
the NSProgress can
include that information
in the localized
Description, so it says,
50 kilobytes-per-second.
So, all these options
can really provide,
help you provide more
information to the user
about what's happening.,
without you having
to localize it yourself.
Now, before we go any
further, we need to talk
about responsibilities.
There are two sides
of NSProgress,
there is the creation side of
it, and the client side of it.
First the creation side.
So, when you create an
NSProgress, you are responsible
for setting its properties
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for setting its properties
and updating the
completedUnitCount
as the work finishes.
So you're creating it, you're
setting the totalUnitCount,
the Kind, setting
keys in the userInfo,
and updating the
completedUnitCount
as the work finishes.
On the other hand, if
you receive an NSProgress
from someone else,
you're the client.
You can get and observe
the various properties,
the totalUnitCount and
completedUnitCount,
the fractionCompleted, or
those localizedDescriptions,
but do not go setting
those properties
because that's just
going to cause confusion
with the creator
of the NSProgress.
So when you create an
NSProgress, you need a way
to give it to clients, and when
you're a client you need a way
to get it.
And one way of doing this is
through the NSProgress reporting
protocol, which we added
in OS X 10.11 and iOS 9.
It is pretty simple, and it
defines one property, progress.
In Cocoa UIDocument
and NSBundleResourceRequest,
both implement this.
And it makes it really obvious
that a class supports
progress reporting.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that a class supports
progress reporting.
All right.
Now, let's do a demo and
take a look at some code.
So, here we have an app.
It is pretty basic.
It has a photo, and you hit this
import button and it downloads
that photo, and when
the download finishes,
it shows it to the user.
Now, that's a pretty
awful user experience,
the user has basically
no idea what's happening.
And we can make it better
by reporting progress
to the user for our download.
So, if we go to our project,
we have our download class
that is used to download the
photo, and it has an initializer
that takes a URL
for it to download,
and it has a completion
handler that gets called
with the downloaded data or
an error if an error occurs.
Next there is a start
method which gets --
which kicks the whole download
off, and we have a handful
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which kicks the whole download
off, and we have a handful
of private methods for various
download-related functionality.
And we also have these
convenience methods
that will get called as
the download progresses.
So we have Will Begin
Download, which gets called
when the download begins
and it gets the total
length of the download.
Did Download Data, which
gets called periodically
as the download completes.
Did Finish Download gets called
when the download is completed,
and Did Fail To Download
is called
if there was an error
during the download.
So what we're going to do is
we want to report progress
about this download
operation, and we can do
that by adopting the
NSProgressReporting protocol.
So let's go ahead and go up
to our class declaration,
and we can add
NSProgressReporting
to our list of classes.
And in order to conform
to NSProgressReporting,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we need a ProgressProperty.
So let's add that,
it's in NSProgress.
And now in our initializer
we need
to create our NSProgressObject.
And since we don't know how
much there is to download yet,
we're going to make our
progress indeterminant.
And one way of making your
progress indeterminant is
setting the totalUnitCount to a
negative value, so we're going
to set our totalUnitCount
to negative 1.
And since we know we're
downloading a file here,
we can also give the NSProgress
a little bit more information
about what's happening.
So we can set the Kind
to NSProgress Kind File,
and we can set in the userInfo,
the NSProgress File Operation
Kind Key to Downloading.
So now we have created our
NSProgress and now we just need
to update it as the
download completes.
So, back to our Will
Begin Download method,
that gets the total
amount to download.
So we want to set our
totalUnitCount to that amount.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we want to set our
totalUnitCount to that amount.
And at that point the progress
will no longer be indeterminant.
And in our Did Download
Data callback,
which is called periodically, we
can set our completedUnitCount
to the number of bytes
downloaded so far.
And finally, in our Did
Download Data callback,
we can set our
completedUnitCount
to the total number of
bytes in the download,
and that way the
progress will be finished.
Now if we, now the user
interface is already using
NSProgressReporting and
looking for the download
to implement that, and will show
the progress, so when we build
and run our app, and we
press the import button,
we have a progress being
reported for the download
to the user, and it is a
much less painful experience.
All right.
So, back to slides.
[Applause]
>> VINCE SPADER: All right.
So, that's the basics
of NSProgress.
Now let's get into what really
makes NSProgress powerful,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now let's get into what really
makes NSProgress powerful,
which is the ability to
compose progress objects
into other progress objects.
Now, just because I press a
download button doesn't mean
that there is really
only one thing happening.
There might be the
download, a verify,
and a decompress operation all
running in reporting progress.
But the user only
sees one progress bar.
So, let's say that these
boxes represent individual
NSProgress objects.
They each report their own
progress in their own units
without having to
worry about the others.
But we want them to be combined
into a single NSProgress
that we hook up to
the user interface.
So we're going to create
an NSProgress object
and call it the overall
progress.
And we can compose
these other progresses
into our overall progress,
the overall progress
becomes a parent
of these other three progress
objects, the download, verify,
and decompress progresses
are its children.
And as those children complete,
the overall progress
will be updated.
So for composing NSProgress
objects, we have this idea
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So for composing NSProgress
objects, we have this idea
of a pendingUnitCount.
The pendingUnitCount
is a portion
of the parent's totalUnitCount
that gets assigned
to a child progress object.
The pendingUnitCount is in
terms of the parent's units,
the child has its own units,
and we say that the parent's
pendingUnitCount is assigned
to the child.
So what happens is, when
a child progress finishes,
the parent's completedUnitCount
is incremented
by the pendingUnitCount
for that child.
So when you have a child,
you don't update the
completedUnitCount manually,
that might conflict with
an update that happens
when your child finishes.
When your at parent
progress, you really want
to assign your entire
totalUnitCount to children.
So let's go over a
diagram of composition.
Let's say we're importing
some photos.
So we have the overall
import NSProgress object.
There are two photos
total, so the totalUnitCount
for our import progress is 2.
And it is going to assign
its entire totalUnitCount
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And it is going to assign
its entire totalUnitCount
to its children, which
are the progresses
for the individual photos below.
So each photo is assigned a
pendingUnitCount of 1 photo
from the overall
import progress.
Now, the individual photo
progresses are similar
but their unit is different,
they have a totalUnitCount
of two steps.
And each step is taking
up one pendingUnitCount,
one for the download
and one for the filter.
And finally we have the
download and filter progresses.
They have their own units.
They have no children.
And they update their
completedUnitCount manually.
All right.
So I brought in the
completedUnitCounts here.
That's the zero of.
And the fraction completed is
the percentage at the bottom.
Since we haven't done anything
yet, it starts at zero.
And let's see what happens
as the completedUnitCounts
at the bottom are updated.
So you can see these -- as
these children complete,
the fraction completed is
updating in the parents.
Progress kind of flows
up to the parent.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Progress kind of flows
up to the parent.
And note that the
completedUnitCount
for the overall progress
is still zero.
The completedUnitCount is only
incremented once the child is
finished and photo 1
still isn't finished.
And once a child does finish,
the completedUnitCount
is incremented
by the pendingUnitCount
for the child.
You can see that the import
progress now has one photo
complete since photo one
is now 100% finished.
The fraction completed, on
the other hand, is multiplying
up based on the pendingUnitCount
and the fraction
completed of the child.
It doesn't wait until the
child is finished to update.
And when everything is at 100%,
the import progress is finished,
and that's an example
of what happens
when you're composing
NSProgress objects.
Now let's zoom in here.
Here we have a progress for
an individual photo's import,
so this is just one photo.
There are the two steps,
download and filter.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
There are the two steps,
download and filter.
So we have a totalUnitCount
of 2.
And we say the download is
going to take one of those units
and the filter is going to
take one of those units.
So each step takes up half of
the photo's overall progress.
But what if these
operations aren't equal?
What if we know that the
filter is pretty quick relative
to the time to download, so it
looks something more like this?
Well, units can be arbitrary.
You can say that there
are actually ten steps
and the download is
assigned nine of those,
and the filter is assigned one.
And now as the download
completes,
the download step takes up
90% of the import progress
and the filter takes
the remaining 10%.
So you can modify the units
of the progress in order
to weight the work being
assigned to children.
All right.
Let's see how to
actually do this in code.
There are two ways of
composing NSProgress objects
and the first way
I'm going to talk
about is implicit composition.
So you create a parent
progress object.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So you create a parent
progress object.
This will be the
photoProgress from before.
So there are two steps.
And we have -- so we have
a totalUnitCount of 2.
And what we want to do, is add
a download progress as a child.
So we call become Current
With Pending Unit Count
on the parent progress,
the photoProgress.
And what this does is it sets
a thread local current progress
so the photoProgress is
the current progress,
and that pendingUnitCount
of 1 is set aside
and basically reserved
for progress to come along
and get added to the
current progress.
And next we call our download
function, startDownload,
and that creates a
progress with the NSProgress
with totalUnitCount
convenience constructor,
and with totalUnit
convenience constructor,
we'll add the created progress
to the current progress.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So the download is added as
a child of our photoProgress.
Next we need to clean this up.
So we call resigned current
and the photoProgress is no
longer the current progress.
And that's it.
Now we've used implicit
composition
to add a child to a parent.
A few things to remember when
using implicit composition,
when supporting implicit
composition,
you want to create the
NSProgress immediately using the
with totalUnitCount
convenience constructor.
That's because the first
progress object added
to the current progress takes
up that entire pendingUnitCount.
And if you create it first
thing, you don't have to worry
about getters or other
calls unintentionally taking
up the parent's
pendingUnitCount.
Also, be sure to document it.
Implicit composition
is implicit after all,
clients won't know you support
implicit composition unless you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
clients won't know you support
implicit composition unless you
say so.
Also, if no child is added
by the time you call
resigned current,
that pendingUnitCount
will be immediately added
to the parent's
completedUnitCount.
So your completedUnitCount
will be updated.
So, the second way to compose
NSProgress objects is new
in OS X 10.11 and iOS 9,
and that's called
explicit composition.
So, you receive a progress
that you want to add
as a child from somewhere.
Perhaps something that conforms
to the NSProgressReporting,
we'll say a filter.
And you have your
parent progress
that you want to add it to.
Let's say the photoProgress
from before.
Then you simply call
addChild with pendingUnitCount
on the progress you
want to add it to,
and path in your child progress,
the filterProgress, here,
and give it the pendingUnitCount
you want to add it with,
since it is one step, we want to
add it with a pendingCount of 1.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
since it is one step, we want to
add it with a pendingCount of 1.
And that's it.
Now your progress is a child
of the parent progress.
So here are some guidelines
for when to use explicit
or implicit composition.
If you have a method that
can't return in NSProgress
like you're crossing an API
boundary you can't change,
use implicit composition
and document
that it supports
implicit composition.
Or, because explicit composition
is new in OS X 10.11 and iOS 9,
you'll have to use implicit
composition on older releases.
Otherwise, you will
generally want
to use explicit composition;
it is a lot simpler.
All right.
Now let's go through
a demo of composition.
So here we have our
photo download class
that we added our progress
reporting to last time.
And if we run our app, it
has been changed a bit.
Now it has a CollectionView
of photos instead
of a single photo.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of photos instead
of a single photo.
So, and when we press import,
instead of just downloading
those images,
it is also running
them through a filter.
So we don't have
progress information
for that overall thing, so
the experience is pretty bad.
And again, we can make it better
by having our operations
report progress.
So let's do that.
So we have our download class
that supports
NSProgressReporting,
and we have our filter class,
which has a class method
that takes an image and
returns a filtered image.
And we have this import class,
which has both the download
and runs the filter when
the download completes.
So it combines the
progress for --
we want it to combine the
progress for the download
and the filter operation.
So let's look at
our photo import.
It has an initializer,
which takes a URL
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and it creates the
download with that URL.
It also has a completion
handler that gets called
with the filtered
and downloaded image,
or an error if an error occurs.
And it also has a start method
for starting the import.
And the start method sets
the completion handler
on the download, and
then creates a UIImage
from the downloaded data,
and then passes that image
into our filter, getting
out our filtered image,
and then calls its
own completion handler
with the filtered image.
And once the completion handler
is set, it starts the download.
So what we want to do is have
our photo import report a
combined progress comprising
of both the download
and the filter's progress.
And we are going
to do that again
by having our photo import class
conform to NSProgressReporting.
So let's go up to
our class declaration
and add NSProgressReporting.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now we need to have a progress
property, so let's add that.
ANd we need to create
our NSProgress object.
And this time for
our units we're going
to use something
a little abstract.
We have run the app a
few times and we've found
that if we have the
download take up about 90%
of the import's progress,
that feels pretty good.
So we're going to have
a totalUnitCount of 10,
and we're going to say
the download takes up 9
of that totalUnitCount
and the filter is going
to take up the remaining 1.
So now in our start method,
since the download already
conforms to NSProgressReporting,
we can just get the progress
object from it and add it
to our progress, and we can do
that with the explicit
add child method.
So we're calling progress
add child download progress,
and our pendingUnitCount is 9,
so it takes up 90%
of our progress.
And now we want to also
add the filter's progress
to our progress.
But the filter is a class method
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But the filter is a class method
that takes an image
and returns an image.
There is no obvious way to
get a progress out of it.
But, if we go to our
photo filter class,
we can see a comment here
that says it supports
implicit progress composition.
So we can use implicit
progress composition
to add it as a child.
So let's go back to our
import start method.
And in the downloads completion
handler, which might be called
on a background thread,
we're going to become current
with the pendingUnitCount of 1.
And note, like I said,
the completion handler
for the download might be
called in a background thread,
but that's okay because we're
calling the filter immediately
after on the same thread.
So, after we become
current, our filter will run
and it will add itself as a
child to the current progress,
and once it returns, we need
to no longer be the current
progress, so we're going
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to no longer be the current
progress, so we're going
to call resign current.
And that's it.
Now we have added both the
download and the filter progress
to our import progress.
And now if we run our
app, and we press import,
you can see the imports
are reporting progress
for each photo.
And that's good, the
user has more information
on what's going on, but it
is not really that great.
We only want one progress
to show to the user.
So let's do that.
So we're going to zoom out a
little bit and we're going to go
to our root View Controller,
photos View Controller,
and this has an overall
progress property.
It is a client of this
NSProgress, it just gets it,
and it is going to hook it
up to the UI and show it.
It is not going to
create it itself.
And it also has a
reference to an Album,
which is the collection of
photos that we're downloading.
And it has IBActions
for the toolbar buttons,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And it has IBActions
for the toolbar buttons,
in this case the
start import button
and that IBAction just calls
import photos on our album.
So if we look at our album
it has an array of photos,
and it creates the photos
from URLs in the main bundle,
and in its import photos method
it goes through each photo
and calls start import on them.
Now if we look at our photo,
our photo has an image URL
that it gets in its initializer,
and it also has a UIImage
property that starts
out as a place holder.
It also has its start
import method
that is getting called
by the album.
It creates that photo
import class
that we added
NSProgressReporting to,
and then sets a completion
handler
for that photo import
class, to set the image
as the image that's
been imported.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
as the image that's
been imported.
And it is then, after the
completion handler is set,
it starts the import and stashes
it away in case it needs it.
So what we want to do is we want
to get the photo imports
progress and collect them
into a progress for all of the
imports that are happening.
So let's go back up to
our root View Controller.
And what we can do is we can say
that our import progress
method returns that NSProgress.
so we're going to set our
overall progress property
to that returned NSProgress.
And import photos doesn't
return in NSProgress yet,
so we need to go do that.
So let's go to our album and
its import photos method.
And it is currently
returning void.
And we need to make it
return in NSProgress.
Then we need to create
the progress object
that we're going to return.
And since this progress object
is going to have a child
for every photo in our album,
we want our totalUnitCount
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for every photo in our album,
we want our totalUnitCount
to be the number of
photos in the album.
And also let's go
ahead and return it.
And what we're going
to do is we're going
to have our photo start import
method also return a progress,
so we're just going to assign
that to a local variable
import progress.
And then we're going to add
that as a child to our --
to the album's progress with
the pendingUnitCount of 1,
since this is the import
progress for one photo.
And now, in our photos
start import method,
it is currently returning void.
We want in to return in
NSProgress, so let's do that.
And since our photo
import already conforms
to NSProgressReporting,
we can just return the
progress property from it.
And that's it.
We have quite a composition
happening now.
We have that overall
progress being assigned
to a progress made up --
that has children for each
import that's happening,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that has children for each
import that's happening,
and each of those imports
has both a download
and a filter child.
So let's run the app.
And now we have an overall
progress at the bottom
that gets updated as all
those children complete.
Removing all of the smaller
progress bars is left
as an exercise.
[Applause]
All right.
>> VINCE SPADER: Okay.
Back to slides.
So now I would like to talk a
little bit about cancellation,
pausing, and resuming.
NSProgress objects can be
a conduit for cancellation.
The creator of the
NSProgress sets cancelable
and the cancellationHandler.
If your operation is doing
some work synchronously and
and the cancellationHandler
doesn't really work,
you can pull the canceled flag
on the NSProgress
object as well.
A client can call cancel and
the NSProgress will set cancel
to true and invoke the
cancellationHandler.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to true and invoke the
cancellationHandler.
So cancellation flows
down to children.
So if you have child progresses
with cancellation handlers,
those will be invoked too.
And it is permanent.
Once a progress is canceled,
there is no uncanceling.
Pausing is pretty
similar to cancellation.
The creator of the
NSProgress sets pausable along
with a pausing handler
and resuming handler.
That resuming handler
is actually new
in OS X 10.11 and iOS 9.
And you can also pull that
paused flag to determine
if the progress is
currently paused.
Clients can call pause
and will set pause
and call the pausing handler,
or they can call resume
and will unset pause and
invoke the resuming handler.
Pausing and resuming also flows
down to child progresses
just like cancellation.
And let's go ahead
and do a demo of that.
So if your objects, if your
operations already support
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So if your objects, if your
operations already support
cancellation, pausing,
and resuming,
it is actually really easy to
expose that through NSProgress.
So we're back in our
photos View Controller.
And this is our root
View Controller.
And this time the app
has a few more buttons.
So if you press import, there is
also a cancel and pause button,
but they don't really
do anything right now.
And we need to hook them up.
So let's do that.
So we have our IBActions
defined for those buttons,
we have cancel import, pause
import, and resume import.
And what we're going to do is
we're going to call cancel,
pause, and resume on the
overall progress inside
of those actions.
And now once -- and now that
will call any cancellation,
pausing, or resuming handlers
on any child progresses.
And we don't have any yet,
but our download does
support cancellation,
pausing, and resuming.
So let's go to our
photo download.
And if we go down to our
Will Begin Download callback,
we can add cancellation,
pausing,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we can add cancellation,
pausing,
resuming support
to this NSProgress.
So first we set cancelable
to true,
and we set our
cancellationHandler,
and here our cancellationHandler
is calling Failed Download
With Error with an
NSUser Canceled Error.
And we also are pausable and
resumable, so we're going
to set pausable to true, and in
our pausing handler we're going
to call this Suspend
Download method.
And in our resuming handler,
we're calling Resume Download.
Now, note that these are all
private, the Failed Download
With Error, Suspend Download,
and Resume Download methods,
so we're only exposing this
functionality for canceling,
pausing, or resuming
through NSProgress,
so it can be a pretty
powerful point of interaction.
Now when we run the app, and
we compress, start our import
and pause, and the progress,
the download pauses itself,
and we press resume,
and it resumes,
and we can hit cancel,
and it gets canceled.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we can hit cancel,
and it gets canceled.
And what's happening is that
overall progress is sending --
invoking any
cancellationHandlers
for any children it might have.
And that's it.
Back to slides.
[Applause]
>> VINCE SPADER: All right.
So let's talk a little bit
about the user interface.
We have gone through
all of this trouble
of creating these
NSProgress objects,
but their ultimate purpose
is to give the user an idea
of what's happening, and that
means the user interface.
So all of the properties
on NSProgress are
key value observable,
clients can add the KVO
observers to update their UI.
For example, a client can update
their UI progress views progress
with the NSProgress as
fractionCompleted property,
or update a label with
a localizedDescription.
Also be aware, that these KVO
callbacks might not necessarily
be called on the main thread.
So if you're updating
UIControls, you want to move
that work to the main queue.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So here is an example of what
that adding an observer
might look like.
You call addObserver for
key path on the NSProgress
for the fractionCompleted
property.
Then in our override
of observe Value
For Key Path we enqueue
some work on the main queue,
and on the main queue we
get the fractionCompleted
from the NSProgress, and
update our UIProgressView.
And that's basically it.
You can use a similar
pattern for updating labels
or buttons in your UI.
All right, finally, let's go
over some best practice
for NSProgress.
Since this talk is called best
practices, I better squeeze some
in at the last minute.
So, first is completion.
Don't use fractionCompleted
to determine completion.
It's a floating point
value determined --
derived from a calculation.
So comparing it to 1.0
won't always be right.
Use completedUnitCount and
totalUnitCount instead,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Use completedUnitCount and
totalUnitCount instead,
unless your progress
is indeterminant
or the totalUnitCount is 0.
It is important, by the way,
that progress is finished.
The parents completedUnitCount
only gets updated
when the child finishes.
And also NSProgress can optimize
a way of completed children
so memory can be saved
as work finishes.
Next, NSProgress
objects cannot be reused.
Once they're done, they're done.
Once they're canceled,
they're canceled.
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.
Finally, performance.
Don't update the
completedUnitCount
in a tight loop.
So, for example, don't update
it every byte of a download.
If you have a parent,
we might be calling
up to update the
fractionCompleted,
which might take longer
than you're expecting
since your composition can be
arbitrarily large and very deep.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
since your composition can be
arbitrarily large and very deep.
But when you do so, don't
forget to finish the progress.
So be sure to update
the completedUnitCount
to the totalUnitCount.
Otherwise you're
going to be left
with nearly finished progresses.
And that's no fun.
So that's it.
We talked a lot about how to
use NSProgress effectively.
If there is anything to
remember, it is these points.
Each progress has its own unit.
You can compose them either
implicitly or explicitly.
And when you do, the
pendingUnitCount is
in the parent's units.
Also you either create the
NSProgress or you're a client.
For localization, you can use
the kind and userInfo properties
to help us give you a
better localizedDescription.
NSProgress can be a really nice
interface for cancellation,
pausing, and resuming.
And all its properties are KVO
observable, so you can use it,
use that to update your UI.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
use that to update your UI.
>> That's it.
For more information,
see the documentation;
also check out the
header for NSProgress,
it is extremely well commented.
We have some new sample
code, photoProgress,
which is based off the
demos I showed today.
If you need any help,
try the developer forums
or contact developer
technical support,
and for general inquiries,
email Paul Marcos.
And that's all.
Thanks.
[Applause]