Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Music ]
(Applause)
>> PHILIPPE HAUSLER:
Good morning.
My name is Philippe Hausler.
I work in the Frameworks
Group, on Foundation.
And today we are going to talk
about NSOperation
and NSOperationQueue.
These are two extremely
powerful classes
that can transform
your application
from running tasks linearly
to a hybrid scenario
of both the object-orientated
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of both the object-orientated
and functionally
asynchronous concepts.
Now, I am pretty sure that all
of you have seen
this application.
The WWDC app uses NSOperation
and NSOperationQueue
extensively.
To be able to accomplish
numerous different tasks all the
way from downloading content
from the Internet all the way
to synchronizing the database
as well as even concepts
like presenting alerts
or displaying videos.
And here to take you more
in depth of NSOperation
and NSOperationQueue and how
it actually was used to be able
to implement the WWDC
app is Dave DeLong.
Dave?
[ Applause ]
>> DAVE DeLONG: Thanks,
Philippe.
[ Silence ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Silence ]
So, my name is Dave DeLong, and
I am a Frameworks Evangelist
at Apple, and I am also
the primary engineer
on the WWDC app, which hopefully
you are all familiar with.
Today we are going to be
covering three main areas
of the WWDC app and NSOperation.
First, we are going to go over
the core concepts of NSOperation
and how you can understand
its API and take advantage
of its powerful state machine.
Next, we are going to go
beyond those basics and look
at the challenges of
the WWDC app across
and how we solved them.
And finally, we are going to
talk about some sample code
that we have provided for you.
So first, let's look
at some core concepts.
Any time you use an NSOperation,
you will always be using
an NSOperationQueue.
And the way to think about
an NSOperationQueue is
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And the way to think about
an NSOperationQueue is
that it's a high-level
dispatch queue.
Hopefully you are all
familiar with dispatch queues
from using Grand
Central Dispatch.
Now, by providing a wrapper
around an NSOperationQueue,
we can gain some
additional functionality.
For example, NSOperationQueue
makes it very easy
to cancel operations that
have not yet begun executing.
While you can perform
cancellation of dispatch blocks,
it is somewhat tricky to do so,
but NSOperationQueue
makes this quite easy.
Another thing that you get
with NSOperationQueue is
a property called the max
concurrent operation count.
And to understand what
this is, let's take a look
at a little animation.
If we set the max
concurrent operation count
of an NSOperationQueue to be 1,
then we essentially make
our NSOperationQueue
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
then we essentially make
our NSOperationQueue
into a serial operation queue.
So let's load up a bunch of
operations onto this queue.
With the max concurrent
operation count of 1,
the queue will pull off
these operations one by one
and execute them in order.
The next operation will
not begin executing
until the previous
one has finished.
That's a serial queue.
However, by default, the value
of this property
is a default value,
which means as many
as the system allows.
So this means that our operation
queue can perform multiple
operations simultaneously
as system resources allow.
So in this case, our operation
queue might be performing two
operations at once.
The ability to change the
behavior of an operation queue
like this can be very powerful.
We don't have to decide
this at creation time
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We don't have to decide
this at creation time
of our operation queue.
So that's NSOperationQueue.
Now let's take a
look at NSOperation.
Where the queue is a high-level
wrapper around a dispatch queue,
you can think of an NSOperation
as a high-level wrapper
around a dispatch block.
Now, in general, NSOperations
run for a little bit longer
than you would expect
a block to run,
so blocks usually take
a few nanoseconds,
maybe at most a millisecond,
to execute.
NSOperations, on the other hand,
can be much longer, for anywhere
from a couple of milliseconds
to even several minutes,
as we will talk about later.
The other thing that's really
nice about an NSOperation is
that since it's a class,
you can subclass it
and provide your own custom
logic on how it executes.
So in order to subclass
NSOperation,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So in order to subclass
NSOperation,
let's take a look
at its lifecycle.
When you create an NSOperation,
it always starts off in a state
that we call the pending state.
So this is the operation
when it's initialized
and as it's being put
onto its operation queue.
Now, at some point,
the operation is going
to become ready to execute,
and it enters the ready state.
And after it becomes ready, the
operation queue will pull it off
of the queue and
begin executing it.
And like I said, this execution
can be anywhere from a couple
of milliseconds to several
minutes to even longer.
After execution finishes,
the operation enters the
finish state, its final state.
So that's pretty simple.
The other thing that
an operation can do is
at any point, it can
enter a canceled state.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
at any point, it can
enter a canceled state.
So let's take a look
at cancellation.
Cancellation on an
NSOperation is defined
as a simple Boolean
property, is canceled.
And the important
thing to understand
about this property is that
it only changes the state
of the property.
When you cancel an operation,
all that's happening is
that a Boolean value
is getting flipped.
So as you subclass
NSOperation, it is up to you
to decide what it means for
your NSOperation to be canceled.
So, for example, if your
operation is performing a
network task, then maybe
canceling your operation is akin
to canceling your
network communication.
Or perhaps if you are
performing some sort
of database transaction
in your operation,
then perhaps canceling
your operation would be
like discarding that
transaction.
So as you subclass NSOperation,
be sure to observe this value
changing and react appropriately
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
be sure to observe this value
changing and react appropriately
if there's any reaction
you need to do.
The other thing to be aware
of with cancellation is it is
susceptible to race conditions.
What do I mean by this?
Well, let's consider an
operation that's executing
in the background, and maybe in
your UI you have a cancel button
that would cancel
this operation.
If the user taps
the cancel button,
it's going to take a small
amount of time for that message,
to cancel, to move
from the main queue
to the operation
in the background.
And if in that small window
of time your operation
finishes executing,
then your operation will
actually never be canceled
because an operation cannot
go from the finished state
to the canceled state.
So it is important to understand
that just because you try
to cancel an operation,
there are some cases
where it won't actually cancel.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
However, if you do need
to cancel an operation,
it is very easy to do so.
All you need to do is
call the cancel method.
So that's cancellation.
Now let's take a look at
this other interesting state
called Ready.
The readiness of an
NSOperation, like cancellation,
is defined as a simple
Boolean property, is ready.
And what this property means is
that the operation
is ready to execute.
So let's take a look at how
this interacts with operations
on an operation queue.
So again, we've got our
serial operation queue,
and we are going to load
up a bunch of operations,
and they are all in the
initial blue pending state.
Now, the first operation
to enter the ready state
is the first operation
that will be executed, even,
for example, in this case,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that will be executed, even,
for example, in this case,
if it's the fourth operation
that was put onto the queue.
So once the operation is
ready, it begins executing.
Then, as other operations
become ready,
they are pulled off
the queue and executed.
And in this case, since
we have a serial queue,
only executing one at a time,
if two operations become ready
at the same time,
then the first one
that has a higher priority
will be pulled off first,
and then the second
one will be executed.
And then, as the other
operations become ready,
they are also pulled off
the queue and executed.
So that's brief look
at readiness.
Now, what can we do with this?
Well, we can make dependencies.
Dependencies are a way for us
to express a strict ordering
between our operations,
that first we want
to execute this thing, and then
we want to execute that thing.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to execute this thing, and then
we want to execute that thing.
And the neat thing
about dependencies is they
provide the base definition
for what it means for an
operation to be ready.
By default, an operation
will become ready if all
of its dependencies
have finished executing.
This is behavior that
you get for free.
The other neat thing
about dependencies is
that they are not limited
by operation queues.
Now, what do I mean by this?
If you have two operation
queues in your application,
operations in the first
queue can be dependent
on the operations
in the second queue.
And we are going to see later
how this can enable some really
powerful patterns.
Now, setting up dependencies
amongst your operations,
again, is extremely simple.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
All we need to do is use
the add dependency method.
So in this case, operation
B will become dependent
on the successful
exectution of operation A.
And so operation B will not
execute until after operation A.
This is guaranteed.
Now, with dependencies, we can
run into a couple of problems,
like operation deadlock.
So if I have an operation
A and another operation B
that is dependent upon the
execution of A, this is fine.
However, if I inadvertently
make A also dependent on B,
then these two operations
will never execute
because they will both be
waiting on each other to finish,
and since they are both waiting,
they will both never start.
So as you are setting
up dependencies
in your application,
don't do this.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in your application,
don't do this.
Now, the WWDC app uses
dependencies all over the place.
And a really simple example is
what happens when you tap the
"add to favorites" button
on a session in the app,
which hopefully you all
did for this session.
When you tap that button,
we're going to first create
an operation called the
login operation.
This is an operation that
guarantees that you have logged
into the app with your
developer name and password.
Next, we are going to create
another operation called the
User Info operation.
This is an operation that
guarantees that the user name
and password are actually a
developer username and password
and not, for example, your
iTunes username and password.
So your Apple ID is an
appropriate developer Apple ID.
Now, favorites in the WWDC
app are stored in CloudKit,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, favorites in the WWDC
app are stored in CloudKit,
so we also need another
operation to make sure
that we have access to
your iCloud account.
So this happens silently,
because we are not
requesting permission
to see your first name
and last name in the app,
so we need to make sure that
you have an iCloud account.
And finally, we can set up
the save favorite operation,
and this is dependent on
the successful completion
of verifying that
you are a developer
and the successful
completion of verifying
that you have an iCloud account.
So that's a simple example.
Let's take a look at a
bit more complex one.
When the WWDC app starts
up, there's a bunch
of setup that we need to do.
First, we are going to download
a small configuration file,
and this file will
tell us small things
like what's the most
recently supported version
of the application,
what features we have
enabled, and so on.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So after we download
this file, we are going
to perform a version
check to make sure
that you are running the
latest version of the WWDC app.
And then after we check
the version of the app,
we can start downloading
useful pieces of information,
such as the news that
we show in the News tab
and the schedule
for the conference.
After we've downloaded
the schedule,
then we can start importing
any favorites that you've saved
to iCloud, any feedback
that you've submitted
so you can see it in the
app, and we are also going
to start downloading
the list of videos.
All of these things require the
schedule to first be in place.
And then finally, we can save
our NSManaged object context,
where we are saving all
of this information.
So let's see how dependencies
and operation lifecycle
affect the execution
of these operations.
And we are going
to move them all
into the Pending
Operations state.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, the first operation
to download the app
settings has no dependencies,
so it immediately
becomes ready to execute.
And so our operation queue
is going to pull it off,
execute it, and then
it's going to finish.
Now, when it finishes,
the version check operation
also immediately becomes ready
to execute, and so it's going
to get pulled off the
queue and executed.
When it finishes, the next three
operations simultaneously become
ready to execute.
So they're going
to start executing.
And as they finish executing,
more and more operations
will become ready to execute.
They will be pulled off
the queue and executed.
Now, the important thing to
realize here and to notice is
that this final operation
to save our context does
not become ready to execute
until everything else
has already finished.
By using dependencies, we can
guarantee that things happen
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
By using dependencies, we can
guarantee that things happen
in the correct order and that
nothing will get out of order.
So now that this one is ready,
it can be executed,
and it can finish.
And App Start-up can continue.
So that's a look
at dependencies.
Overall, NSOperations
are a fantastic way
to abstract logic in your code.
By putting our logic inside of
operations, it makes it easier
to simplify these logic
changes because we are dealing
with isolated pieces of
work, much like we do
when we are dealing
with a block.
As an example of this, the
WWDC app this year moved
from being -- from saving
your favorites and feedback
on a custom backend,
to being on CloudKit.
Now, at this point I want
you all to think about,
what would it take to move
your application from a custom,
from whatever service you
are using now to CloudKit?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
from whatever service you
are using now to CloudKit?
And if you are suddenly
panicking, and all of the places
in your code where you've
got network communication,
and all these dependencies
on, you know, the intricacies
of your server provider,
then this is a good sign
that you should be
using operations.
In the WWDC app, all
of our network communication
is hidden behind operations,
which means that in order
to change the backend
from using a custom
service to using CloudKit,
all we had to do was
rewrite four small classes.
It took us less than a day
and then a couple more days
to successfully test
our changes.
It was a simple, trivial change.
Now, in all of this, you
might be wondering, well,
what about Grand
Central Dispatch?
Grand Central Dispatch
absolutely has its place.
In fact, when you
download the sample code
for this presentation and look
through it, you will see places
where we are using Grand Central
Dispatch in that sample code
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for things that are not really
appropriate to NSOperations.
So for example, anytime
you simply need
to bounce a method call from
one queue to another queue,
you don't need to wrap
that in an operation.
That's something you want to
keep fast and very lightweight.
Or if you are doing
anything with semaphores
or dispatch group, these
are all perfect use cases
for Grand Central Dispatch.
So that's a look at the basics.
Let's go beyond them.
Now, one of the things that
we realized in the WWDC app is
that there are places where
we want to have UI interaction
but still have it participate
in the operation chain.
So for example, authentication.
We talked earlier about
saving a favorite.
We need to make sure
that you are logged in.
But what if you are not?
Well, we realized that
we can put UI elements,
UI functionality
within our operations.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
UI functionality
within our operations.
So for example, the
authentication dialogue
that slides up in the WWDC app
is actually an NSOperation.
Or anytime that you are watching
a video in the WWDC app,
we encapsulated this inside
a "watch video" operation.
So all we need to do is
create one of these operations
with the appropriate
video asset and put it
on our operation queue,
and everything else will
just fall into place.
Even more broadly, any time you
see an alert in the WWDC app,
this is something that we
thought was also a really good
use case for putting UI
inside an NSOperation.
And we discovered that the
underlying principle we found
here was that when we're dealing
with any sort of modal UI,
so a UI that takes over
generally the entire real estate
of your application, this
is an excellent thing
to encapsulate inside
of an NSOperation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to encapsulate inside
of an NSOperation.
So to reiterate, the first
time you launch the WWDC app,
you saw this dialogue asking
if we could collect
some simple usage data
on how you are using the app.
This dialogue that appears,
this UI alert controller,
is actually being run from
inside an NSOperation.
Or the login sheet.
If you try to add something
to Favorites or leave feedback
on a session, this is
also an NSOperation.
The next thing we encountered is
that there are some times we
want to perform simple pieces
of logic as a block,
but we also wanted
to participate inside
the operation mechanism.
So we turned to block
operations, NSBlock operation
and other custom
operations that we created.
So this is really just an
NSOperation to execute a block.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this is really just an
NSOperation to execute a block.
And you may be asking, well,
if NSOperation is just an
abstraction around a block,
why would I then return to using
blocks inside an NSOperation?
And that's because by putting
a block inside an NSOperation,
you gain all of the great
features of NSOperation
for that block that you do
for NSOperation,
such as dependencies.
Let's take a look at
what we can do with this
and see what happens when you
tap the Leave Feedback button
in the WWDC app.
Well, the Leave Feedback button
wants to perform a segue.
It wants to present
the view controller
where you can leave some
five-star ratings or maybe four
if they were really good
but not truly excellent.
We want to perform this segue.
So we are going to put this
segue inside of a block,
and then we are going
to put this block inside
of a block operation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to put this block inside
of a block operation.
Now, we only want to allow
you to leave feedback
if you've signed into the app.
So we need to verify
that you've signed
in with your developer
account, just like we do
when you save a favorite.
And in order to verify that
you have a developer account,
we need to make sure that
you are logged in at all.
So by putting the
perform segue call inside
of a block operation,
we can guarantee
that we will never
present the login sheet
until after you have logged in.
This is really powerful.
We have described a
really complex behavior,
a sequence of things
that must occur simply
by using operations
and dependencies.
Now, as we were writing
the WWDC app,
we noticed that there were some
cases where we were doing a lot
of the same operations
over and over again.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of the same operations
over and over again.
So for example, we've
already seen this login
and user info operation
a couple of times.
So we thought wouldn't it
be great if there were a way
where we could just
automatically have those
operations created?
So we came up with a
way for an operation
to generate its own
dependencies.
In other words, we're expressing
the idea that we never want
to execute this thing
without always executing this
other thing.
So again, let's take a look at
saving a favorite to CloudKit,
or perhaps downloading
a pass, or really,
anything in the WWDC app that
requires you to be logged in.
So when you tap the "add
to favorites" button,
all we are really doing is
creating a single operation
to save the favorite.
And this is going to
encapsulate some small pieces
of information, the session
identifier and whether
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of information, the session
identifier and whether
or not you want it added
to favorites or removed
from favorites, a
little Boolean flag.
Now, this "save favorite"
operation knows
that it requires
permission to run,
so it is automatically going
to generate two dependencies,
the one to check that you
are a developer and the other
to guarantee that we have
access to your iCloud account.
Now, the operation to guarantee
that you are a developer
itself needs to guarantee
that you are logged in, so it
generates its own dependency
to make sure you are logged in.
And so we are able to keep
our app code quite simple.
We only need to create
a single operation,
and then it automatically
generates its own dependencies.
And perhaps later, if we decide
to remove the requirement
that you need to be logged
in to save a favorite,
then we simply remove
the small line of code
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
then we simply remove
the small line of code
that instructs this favorite
operation to generate
that particular dependency,
and we have now removed
that requirement across
the entire application.
We don't have to go
through every single place
where we have an "add
to favorite" button
and modify code there.
Now, we also wanted to
make sure that other kinds
of conditions were met.
We wanted to be able to expand
upon this idea of readiness,
expand upon the idea
of when we're allowed
to execute an operation.
Some examples that we came
up with include, we only want
to execute this operation
if you are actually
connected to the network.
If you try to add the
favorite in while your phone is
in airplane mode, for
example, we, of course,
don't want to try executing
our CloudKit operation.
We also want to guarantee,
perhaps, that maybe we only want
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We also want to guarantee,
perhaps, that maybe we only want
to execute an operation if we
have access to your location.
So we need a way to
express this as well.
Or for example, we only want
to execute certain kinds
of operations if you are
actually logged into the app.
So by extending the concept of
what it means for an operation
to be ready, we can make our
operations even more powerful.
So hopefully you won't ever
see this error, but if you do,
this is an example of
an operation failing
because it was never able to
become fully ready to execute.
In this case because
it was unable
to connect to the network.
So extending the readiness
concept can also be
really powerful.
Next, there were a couple
of operations where we found
that they were always
being done together.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we thought, wouldn't it
be neat if instead of having
to create the same sequence of
operations over and over again,
if we could just create
one operation and then
under the hood it would
create the same sequence
of operations for us?
A common example of this is the
idea of downloading something
and then parsing it to
save into a local storage.
I am sure this is a
concept that almost all
of you are familiar with.
So let's take a look at how
we can compose operations
to make them simpler.
So let's say we have a
generic import data operation,
and then it's dependent
on something
and other things
are dependent on it.
We have this import idea.
Well, we want this to actually
do two things, so it's going
to wrap another NSOperation, and
this operation is simply going
to perform the download.
It's a single, isolated
piece of work.
And then it's going to
create a second operation
to parse whatever was
downloaded and make it dependent
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to parse whatever was
downloaded and make it dependent
on the download operation
so that parsing will always
occur after downloading.
Now, by encapsulating
those two operations inside
of a larger operation, we can
now easily change perhaps,
maybe where our data is coming
from, what format it's in,
and even how we handle errors.
And we only have to
do this in one place,
inside our import operation,
because that's the only thing
that the rest of
our app knows about.
Now, you don't always know
ahead of time, perhaps,
the exact operations
that you need to perform.
In the WWDC app, we cannot know
at compile time how many
favorites you've saved
to CloudKit, so we
needed a way to be able
to dynamically compose
operations.
So we created this wrapper
called a Fetch Favorites
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we created this wrapper
called a Fetch Favorites
operation, and since
we are using CloudKit,
under the hood we are going to
perform a CK query operation,
because CloudKit is also
built on NSOperation.
So we are going to perform
our first query operation.
And maybe you have every single
session favorited at WWDC,
so this is going to indicate
that there are still
more favorites to fetch.
So we are going to keep on
executing query operations
until we have received a
response that that's all of them
and we've got them all.
So by using this
composition model,
we can still simply express our
operation chain as a single --
with a single "fetch favorites"
operation, but under the hood,
actually be performing
many operations,
potentially, in sequence.
Now, in the code, it
looks something like this.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Our operations have
an execute method,
and this is where they all
start doing their work.
So the first time the fetch
favorite operation starts
executing, it's going to
set up the initial query.
We are going to look for session
favorite records created by you.
So we are going to construct
our query operation and pass it
to this method called
execute query operation.
And this is the execute
query operation.
As this query operation
completes, we are going
to first check, was there
an error, and if there was,
let's abort the process
and handle the error.
If there wasn't an error, but
instead, there was a cursor,
this is how CloudKit tells us
that there are still more
things for us to fetch.
So we're going to create
the next CK query operation
in the sequence using
this cursor
and semi-recursively call
this execute query operation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and semi-recursively call
this execute query operation.
And this is how we can be
executing many query operations.
And then if we get neither
the cursor nor the error,
this is how CloudKit indicates
that we have fetched everything,
and so we can begin
to import the records
that we have downloaded.
Next, during development, there
were some times when we came
up with some visual glitches,
the things that we thought
were visual glitches.
Now, perhaps you've all had
the experience of using an app
and an alert pops up,
and then as you are
about to tap the button,
another alert pops up.
And you think, oh, great, man,
what is even going on now?
And as you are about
to tap that button,
maybe another alert pops up,
and with all of the animations
of coming and going, you are no
longer even sure if you are back
on the first alert or if
you are now on the third.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We really wanted to avoid
this confusing scenario.
Another thing we wanted to
do is we wanted to guarantee
that you could never,
ever try to watch more
than one video at once.
This is something that the
WWDC app does not know how
to handle correctly, so
we wanted to guarantee
that no matter what you did,
we would never allow
you to do that.
Another thing we
wanted to guarantee is
that we would never try to load
our underlying database more
than once.
So we came up with a way of
describing mutual exclusivity,
the idea that only one
of these particular kind
of operations can be
running at a time.
Now, you are probably
thinking, wow,
this is a really complex idea.
How would we even do this?
And it is really simple.
So let's go back to
the alert example.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So let's go back to
the alert example.
Let's say we create an operation
to display alert and alert
to the user, and we put it
onto an operation queue.
And maybe it's there waiting
for something else to finish,
maybe it's already in
the middle of executing.
Who knows?
But then something
happens, and we decide
to create another
alert operation.
Well, all we need to do is
make the second alert operation
dependent on the first one.
And this is where
cross-queue dependencies are
really powerful.
Because it does not matter
which queue this alert
operation is executing on,
as long as the second operation
is dependent on the first,
then the second operation
will never execute
until after the first
operation completes.
And so for some -- if
for some insane reason,
we decide to create
more alert operations,
even more alert operations,
as long as we set
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
even more alert operations,
as long as we set
up these dependencies of the
next operation being dependent
on the previous, like a singly
linked list back in time,
we are guaranteeing that
our operations will be
mutually exclusive.
This is really powerful.
By using dependencies,
we can guarantee correct
behavior in our application.
We can guarantee that
you will never see more
than one alert at once.
We can guarantee that you will
never be able to watch more
than one video at a time.
We can guarantee that we will
never try to load two copies
of our data store
simultaneously.
So those are a taste of some
of the challenges that we came
up with when writing
the WWDC app.
There are more.
But we think these
ones are really cool.
And we came up with what we
thought was a pretty neat way
to solve them.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to solve them.
So let's talk about
the sample code.
On the WWDC website, under
the sample code section,
you can find a piece
of sample code called
Advanced NSOperations.
And this is a simple app
to show recent earthquakes.
But under the hood, it's built
entirely on NSOperations,
and the operations that it's
using in the app is code
that we have extracted
from the WWDC app
and put into the sample.
And this is code that's
been in the app, actually,
for a couple of years.
It is stable.
Now, the primary class that this
sample code uses is operation.
And this is a basic
subclass of NSOperation.
And in the sample code, this
operation adds two key features.
The first is the
idea of a condition,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The first is the
idea of a condition,
which we will talk
about in a second.
And the second is a concept
that we call "observers."
Now, we've got a bunch
of different kinds
of operations in
the sample code.
We have group operations,
so it's very easy
to make operations internally
perform more operations.
We also have an operation
subclass in the sample code
that allows you to take an
NSURLSession task and wrap it
up inside of an NSOperation so
that you can make it, perhaps,
dependent on something else
or make other things dependent
on this, or perhaps add
conditions or observers to it.
There's a simple operation to
request your current location.
There's one -- because
it's sometimes useful --
to just simply wait
a little bit of time.
There's even an operation
to show an alert to the user
with buttons and block handlers.
So lots of great kinds
of NSOperation subclasses
in the sample code.
Now, this operation has
a concept of a condition.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, this operation has
a concept of a condition.
And a condition is a
protocol that we have defined,
and it's a way for an operation
to express how it
generates dependencies,
how it defines mutual
exclusivity,
and also how it extends
the concept of readiness.
So some kind of conditions
that we have provided
in the sample code.
One is the mutually
exclusive generic condition,
and this is a way of describing
that an operation is mutually
exclusive with other kinds
of operations with
the same generic type.
We have a reachability condition
in there, so you can simply,
with one line of code, express
that an operation
can only execute
if the network is reachable
at a very high level.
And we've got a plethora
of permission conditions,
such as only execute this
operation if we have access
to a certain CloudKit container,
or only execute this operation
if we have access
to your calendar
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
if we have access
to your calendar
or to your photo library
or to your contacts
or whatever else you'd like.
So that's conditions.
And the final piece is
operation observers.
An operation observer is again
a protocol type, and it's a way
for this value to be notified
about significant events
in operation lifecycle, such
as start or the beginning
of execution, the
end of execution,
and also if the operation
decides
to produce another operation
that should be executed later,
such as if an operation decides
that it failed and it wants
to show an alert, it can produce
or generate an alert operation.
And we have a couple of examples
of observers, such as timeouts.
By simply adding a timeout
observer to an operation,
that observer is going
to watch to make sure
that the operation completes
within whatever time
interval you specified,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
within whatever time
interval you specified,
and if it takes too long,
it's going to automatically
cancel it.
One that I think is really
neat is a background observer,
so this is an observer that
when you attach it to one
of these operations is
going to watch the state
of your UI application,
and if your application
enters the background,
it's going to automatically
begin a background task,
and then automatically end it
when the operation
finishes executing.
So if you have some sort
of critical operation,
perhaps you are uploading data
to a server and you don't want
that to be interrupted
or suspended,
one way you can accomplish
this is by adding one
of these background
observers to the operation,
and it will guarantee
that you have some time
in the background during which
you can complete this operation.
And another one that's really
cool is the network activity
indicator observer.
This is a simple observer you
can attach to an operation,
and when it begins, it's
going to increment a sort
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and when it begins, it's
going to increment a sort
of retain count on the
activity indicator spinner
in the status bar, and then
when the operation ends,
it's going to decrement
that retain count.
So you can have multiple
networking operations in flight
at the same time, and
by simply attaching one
of these network indicator
-- or, observers --
it will automatically show
and hide the network activity
indicator as appropriate.
It is no longer this crazy --
crazy state that you
have to manage yourself.
It all kind of happens
automatically.
It's really cool.
And then there are other
observers that we have
in the sample code for
you, such as being able
to attach arbitrary blocks
to one of those three events
and have them react
appropriately.
So that's a quick look
at the sample code.
On the surface, it looks like
a really simple application,
but under the hood, there is
lots of really meaty goodness,
and I really encourage you to
download it and check it out.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, in summary, use operations
to abstract the logic
in your app.
By putting your logic inside of
operations, it becomes very easy
to change it later, such as
how we converted the WWDC app
to use CloudKit.
It was a simple change for us.
Use dependencies to
express the relationships
between your operations.
It makes it very simple
to guarantee certain kinds
of behaviors, that B
must always follow A.
Next, operations allow you to
describe complex behaviors,
such as mutual exclusivity
or composition.
These are all simple
with operations.
And overall NSOperation
allows you
to perform some very powerful
things with very minimal effort.
So we have a couple of
related sessions for you.
Immediately after this
session is "Building Responsive
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Immediately after this
session is "Building Responsive
and Efficient Apps with GCD."
We don't want you
to leave GCD behind.
It is still a perfectly
appropriate technology to use.
So I encourage you to go to
this session or watch the video
and see when you should
be using GCD in your apps.
And then if you want to see more
about how our frameworks use
NSOperation, you can watch the
"CloudKit Tips and Tricks"
session from this year
or the "Advanced CloudKit"
session from last year.
Like I said, we have sample code
available on the WWDC website.
I encourage you to check it out.
I also want you to read the
'Threading Programming Guide'
in the Developer Library.
This has a lot of really great
information on other ways
that you can use NSOperation.
And if you need any technical
support, we encourage you
to post your questions in the
Developer Forums or contact DTS.
Thank you very much, and have
a great rest of the conference.
[ Applause ]