WWDC2015 Session 224

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> SOPHIA TEUTSCHLER:
Good afternoon.
[ Applause ]
Welcome to App Extension
Best Practices.
I am Sophia Teutschler,
I am an engineer
in the UIKit Framework Team.
Later in this session I will
be joined by my colleague,
Ian Baird from CoreOS.
The first part of the
session I would like to talk
about the two main types
of extensions on iOS,
Action and Share extensions.
The later session, Ian
will join us to talk
about Today Widget enhancements.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Throughout the session we
will show you many real-world
examples to help
you make the most
out of those types
of extensions.
Now, Action and Share
extensions.
Let me give you a brief
overview of the two types
of extensions, how they differ.
Now, Action extensions are all
about changing content in place,
where the Share extensions
are all about moving content
from the current
host application
to your application
or Web Service.
Now, let me give you
more examples here.
As I just mentioned,
Action extensions act
on a current content.
Because of that,
they use the content
as the main user interface.
I will show an example
later on about that.
But you also have
the opportunity
to present additional options
before you perform that action.
Also, your application
functionality can be separated
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Also, your application
functionality can be separated
in different Action
extensions that live
within that container
application.
Now, let me show
an example here.
We are in Safari.
I am going to tap on the
action item on the top right,
bring up our Share Sheet with
our two types of extensions,
the Share extension
is on the top
and the Action extension
on the bottom.
We made this Translate
extension, and when we tap it,
the following will happen.
Safari will transfer a
webpage to that extension.
There it is translated and then
the translated corners move back
into place in Safari.
Here's a variation
of the same one.
When you tap Translate, you
bring up this form sheet
where a user can select
one of the languages.
Then when you tap Language,
we transfer the webpage
to the extension, then again
it's translated and moved back
into place in Safari So so
much about Action extensions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
into place in Safari So so
much about Action extensions.
So how are Share
extensions different?
As I said earlier,
Share extensions are all
about sharing content
from the host application
to your app or Web Service.
And because of that,
it's very important
that you offer the user
to validate and edit
that content before it moves
out to your Web Service.
And we offer you an API for that
that I will show you a
few examples on later on.
Also, your Share extension
represents your application
in the Share Sheet, and
to prevent confusion,
we only allow you to offer
one Share extension per
containing app.
Now, let me show an example
for the Share extension here.
We have the Share in the bottom
and our Share extension
on the top.
Now, we select four
photos, and when we tap
on the iCloud Photo
Sharing extension,
the following will happen.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
the following will happen.
We get a little preview sheet
where a user can add a comment,
maybe tap the Shared album, and
a little preview on the right.
But we haven't transferred
any data yet.
Now, if the information is
correct, the user can decide
to either cancel out or tap Post
to actually perform the Action.
Now let's take a closer look at
this sheet here in the Center.
This is actually the API I
was talking about earlier.
It's called the
SLComposeServiceViewController.
This is how it looks
straight out of the box.
There's a text field, a
little preview on the right.
However, it's very customizable,
and for our special service,
for example, we like
to customize it
to make it look more like this.
We added a place holder,
we even added a character
remaining property.
We even change the preview view,
and we have some options
on the bottom part.
Now let's go through some
examples to build that up.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now let's go through some
examples to build that up.
First, to set a place holder,
set a place holder
property on that sheet.
There we go.
And your Web Service might
have a maximum character limit,
so we have a little indicator
on the bottom left for that.
To update it, just
set a property there.
And again, we offer you a
preview view out of the box,
but you can have your own one.
To support that simply subclass
the composer's controller
and override load preview view,
and then return your custom
view controller for the preview.
One thing to keep
in mind, however,
make sure that the view has --
otherwise it might not
show up on the sheet.
Now, there are these
options on the bottom.
Those are simply cells
with a value on the left,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Those are simply cells
with a value on the left,
value on the right,
and to tap on them,
we push an option
view controller
that can select your options,
your custom options there.
Let me show you how you
set that up in code.
Pretty simple.
Subclass SL -- composer,
then override the -- method.
Each row is represented
as an SL compose sheet
configuration item.
Quite a mouthful.
On the left is the title.
The value is on the
right-hand side.
Now, if the user taps on a cell,
we call this tap handler here,
and there you are supposed
to create your custom
view controller
for your custom option.
And to push it, simply call push
configuration view controller.
This works the same
way as the API
from your navigation controller.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then finally,
return the area of items.
Now, we even offer you
auto complete support
on the compose sheet.
To auto complete, you
set a view controller
to update the button part here.
In this case, it's a table view
where we auto complete names,
but it could be really anything.
To set it, just set the auto
completion view property,
we do all the animations
for you.
When you are done
auto completing,
you set it back to nil.
All pretty simple.
Again,
our
SLComposeServiceViewController
provides a consistent
and familiar UI.
It's very customizable for
you to adopt it in your app.
However, if you have
different needs
for your service extension,
you can always go back
subclassing your view
controller directly.
Now, I will just show you how
to make Action Share extensions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, I will just show you how
to make Action Share extensions.
Now let's change
gears a little bit
and discuss how you can support
extensions made by someone else
in your host application.
For example, let's say we
made a text edit application,
and we'd like to share
those text documents
as well, plain text.
However, some extensions
might not understand text.
They might only support PDFs
or other extensions
might only support HTML.
Now, you would like to support
as many extensions as possible,
so a good way would be to offer
all those three file types.
But when you think about that,
those are not really
three separate documents
when you share a text document,
but what you really have
is a single document
that supports different
file formats.
And exactly for that we have
an API called NSItemProvider.
In one sentence,
NSItemProvider is a single item
with multiple representations.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with multiple representations.
Let me show you a code
example to explain it better.
Now, we create NSItemProvider,
then we call register the item
for type identifier to
add a new representation
to that item provider.
In that case, it's plain text.
Now, if the extension asks for
the plain text representation,
the system will call this
load handler block here.
And there you are supposed
to create your text document
on the fly and call
the completion handler
to return the data
back to the extension.
Same for PDF.
As soon as the extension asks
for a PDF representation,
we call the load handler, there
you create your PDF on the fly
and call the completion
and let it return data back
to the extension.
To display Share Sheet,
simply create a UI
activity view controller
and offer the item provider
options in the initializer.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and offer the item provider
options in the initializer.
Now, in the first example with
the compose sheet you saw,
we have this little
preview in the top right.
The hosts should offer
previews as well.
Those previews represent
what will be shared,
and also they need to be
simple and efficient to create.
They can't be too large.
Again, for that, we have an
API in NSItemProvider as well.
It's called preview
image handler.
And as soon as the
extension asks for a preview,
we call this block here,
and there you are supposed
to create your thumbnail or
your preview representation
as an image on the fly and
call the completion handler
to return that data back.
Now we need to change our
perspective yet again.
We talked first about
implementing extensions.
Then we talked about supporting
extensions as a host app.
Now let's look at what it's
like to be an extension
developer again and how
to declare your support
with the kind
of data the host app can
offer to your extension.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of data the host app can
offer to your extension.
For example, maybe you like
to have full sharing service,
and you want to declare that you
are sharing extension support
images and movie.
Now, let's make three
extensions in that case.
One extension should support --
one extension supports images,
another one can handle movies,
and a third one handles
images and movies.
Now let's see what happens
if the host app shares
the single image.
In that case, the first
and third extension will
appear in the Share Sheet.
Now, same way for
the movie case.
If the host application shares
a single movie, the second
and third extension in the
Share Sheet, that makes sense.
However, what happens
if the host
of a share supports
image and movie?
You would think all the
extensions would appear
in the sheet; however, this
is currently not the case
since in iOS 8 the
implementation requires
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
since in iOS 8 the
implementation requires
that extension can handle all
two types of file formats.
It's a bit unfortunate
since user extension developer
would probably like to support
as many extensions as possible,
and to do that, you should offer
as many representations
as possible as well.
But in that case, you
actually get less extensions
than by just sharing a
single representation,
like movie or image.
So we added a new
behavior in iOS 9.
You can opt in by adding
the NSExtension activation
dictionary version
in new extensions info
plist, set it to two.
After that, your
extension will appear even
if the host app shares
both image and movie,
and even if you are only
interested in movies or images.
Now, those are high-level
activation rules,
but we also support activation
rules that are just predicates.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but we also support activation
rules that are just predicates.
Again, predicates can be
very simple but also very,
very powerful like
this one here.
I won't go into details
right now,
but check out the App
Extension Developer Guide
for more information.
Now, let's talk about
icons for extensions.
As I mentioned before,
Share extensions
represents your application
of Web Service within
a Share Sheet.
Because of that, we simply
use your application icon
or a containing applications
icon as the icon
in the Share Sheet, so
there's no additional work
for you needed.
Action extensions, on the other
hand, require template images.
We require two sizes, one for
the iPad, one for the iPhone.
And they reside in
the extension bundle.
Now, let me explain how
template images work.
Think of it as, like, a stencil.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Think of it as, like, a stencil.
A template image is a
black-and-white representation
with a transparent background,
and the system then takes
that stencil of that
black-and-white image
and creates the actual
icon for the Share Sheet.
And again, require two sizes,
60 points for the iPhone,
76 points for the iPad.
However, I encourage you to use
image assets in your extension
and offer all different --
all the different icon sizes
that are for application icons.
That way your app extension
will be far more future-proof.
So much about Action
Share extensions.
Let's invite Ian
on stage to talk
about Today Widget enhancements.
[ Applause ]
>> IAN BAIRD: Thank you, Sophia.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Hi. I'm Ian Baird, CoreOS
engineer, and today I am going
to talk to you about
Today Widget enhancements.
First a quick recap.
As you remember from last year,
Today Widgets give you quick
information at a glance,
so your stock prices,
your sports scores.
It tells you how long
it's going to take for you
to get home from work.
So today I am going to
talk to you about how
to enhance your Today
Widget, how to make sure
that the model data is
always up-to-date and in sync
with your containing app.
I'm also going to tell
you about how to make sure
that your visual
representation is up-to-date
and reflects your
widget's content as well.
And then I am going to go over
some general best practices
that you can use for
all of your extensions,
including your Today Widgets.
Let's get started.
This is a view of the
Notification Center.
It's populated by Today Widgets.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's populated by Today Widgets.
You can scroll it to
see all of the widgets,
and if you want more information
about anything you can see here,
you can simply tap on the
widget, and it will take you
to the containing app
for more information.
Now, how do we facilitate
this interaction
between the Today Widget
and its containing app?
Well, we do this by
the use of URL schemes.
And now I am going to tell you
a little bit more about those.
Interactions use these URL
schemes and some open URL API
on NSExtension contexts to
take the user to the app.
Now, we have some best
practices that I'd like to talk
about today governing the
use of these URL schemes.
First, we'd really like
you to use and concentrate
on your app's registered
URL schemes.
This is great.
The next thing you want
to use is you can use system
URL schemes, like HTTPS.
That's going to open
a webpage in Safari.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
That's going to open
a webpage in Safari.
You can also create
messages, start phone calls,
and perform other interactions
and start interesting workflows
with system components.
And here's how you do it.
You'll notice that I am showing
a table view, did select row
at index path callback
because a lot
of widgets are simply
table views.
The first thing we
are going to want
to do is actually
construct a URL.
We are going to do this with
the myapp URL scheme here,
just for the sake of example.
This is going to take
us to my containing app.
The next thing we
are going to want
to do is we are actually
going to want to call
that open URL API that you can
find on the extension context,
which is probably attached
to your view controller.
Now, this is a little bit
different from the API we expose
on UI application in that
it takes a call-back block,
and this call-back block
has one boolean parameter
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and this call-back block
has one boolean parameter
that I am going to tell
you more about now.
The success parameter is
going to be set to true
if we were able to take
you to the containing app
or to the system component,
which has registered
the URL scheme.
It's going to be set to false
if we were not able to do this,
like if the user has not
unlocked the phone and pulls
down the Notification Center
when the phone is locked.
There are many other
ways of interacting
with the containing app and
sharing data, and today I want
to tell you about
how to use defaults
with your containing
app; containers;
Keychain items; and
framework data.
Framework data is going to be
scribbled into Shared containers
on your behalf by
system frameworks.
And all of this is neatly
grouped up into app groups.
First, user defaults.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
First, user defaults.
You probably know
what user defaults are
if you've been developing
for Cocoa
or Cocoa Touch for a while.
They are small pieces
of configuration data.
They are like little
strings, numbers, booleans,
and other things that affect
the configuration of your app.
You can share these pieces
of configuration data
between your containing
app and your extensions
by using the NSUser defaults
init with suite name API.
You are going to pass the app
group identifier to this API.
Now there's an important
thing to remember about this,
this API doesn't merely unlock
your containing app's defaults,
your standard user defaults
in your containing app.
What it does is it
creates a new default suite
which your containing
app can also have access
to if it participates in the app
group, so it's super important
that you use this API, not
only in your extension,
but in your containing
app when you want
to change these configuration
items in the defaults.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to change these configuration
items in the defaults.
The next thing I want to
talk to you about are things
that you can use inside
of the shared container
which the containing app and its
extension all have access to.
The first thing you can
keep in there is model data.
Model data is stuff like SQLite
files, core data data stores
and maybe even model
objects persisted
to things like plist files.
You can store all of this
inside of a shared container
where the containing application
and its extensions can
all have access to it.
You can also store
documents in there.
Earlier, Sophia was talking
about a text application,
and maybe this text application
should store its text documents
inside of the shared container,
where it can edit them
and the extension can edit it.
Next, you can also store
media, media items like images,
video clips, audio files, and
other things that you want
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
video clips, audio files, and
other things that you want
to have accessible to both
your containing application
and its extensions.
Now, if you are storing
cacheable items
in the shared container, that's
probably not all that good.
I think you want to keep that
in caches where it can be purged
if we start running
low on space.
Now, to set up core data to
use the shared container,
I want to show you a
little bit of sample code,
and this is sample code that
you are going to use both
in your containing app
and any of its extensions.
You are simply modifying the
code that Xcode already produced
for you via the template.
So the first thing you will want
to do is create a new property
or change the existing one.
And in this case, we are calling
it secure app group presentation
store URL.
Of course, the first thing we
need to do is get an instance
of the NSFile manager.
And next, on file manager, we
are going to call container URL
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And next, on file manager, we
are going to call container URL
for security application
group identifier, and again,
pass the app group identifier
that we had set up previously.
After that, we are going
to append the store
file name to that URL.
That URL points in to
the shared container,
where both the containing app
and the extension can access it.
Now we need to use this store
URL, so we are going to set
up our persistent
store coordinator.
And the way we are
going to do this is
by first creating a store
coordinator using our manage
object model, and then we are
going to grab the instance
of the store URL
we just created.
Finally, we are going to
add this persistent store
to the store coordinator
using the URL we just created.
This is going to
point -- remember --
to the SQLite file that's
backing this persistent store
in the Shared container.
Finally, we return
it to the caller.
Lastly, we actually want to set
up the manage object context,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Lastly, we actually want to set
up the manage object context,
and we will do this by grabbing
our persistent store coordinator
and then creating a
manage object context.
And then we will
set this coordinator
to be the persistent
store coordinator
for the manage object
context and then return it.
And that's all you have to
do to set up core data inside
of your containing app
and any of its extensions
to use a shared persistent
store which is located
in the shared container.
So core data may not be what
you need for your application.
Let's pretend that you are using
the plist files that I talked
about earlier to
serialize your model objects
into the shared container.
At this point, you are
going to have to worry
about synchronization, access,
and all this kind of stuff
in the shared container because
very potentially, your extension
and your application
could be attempting
to simultaneously
modify these files,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to simultaneously
modify these files,
which is not good
for data consistency.
So you may actually have
to use exclusive locks.
And you need to be
really super careful
about using exclusive locks for
data in the shared container
because when an extension
is killed,
if it's suspended while
holding on to an exclusive lock.
Again, this isn't good
for data consistency.
So what do you need
to do about this?
Well, I'd like to talk
to you a little bit more
about task assertions.
Task assertions are a great
way for your extension
to tell the operating system
that, hey, I'm doing something
that you probably
shouldn't interrupt.
And if you interrupt it, well,
I'd like to get some sort
of call-back so I can
clean things up first.
Remember that extensions are
suspended pretty aggressively
when they are no longer in use.
When the user swipes down to
expose the Notification Center
and then swipes back up to
dismiss it, we are going
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and then swipes back up to
dismiss it, we are going
to suspend all of the
extensions in play at that time.
So that could be pretty quick.
So you are going to want
to protect serialization
and other cleanup tasks
with these background
task assertions.
And I am going to
show you how to do
that now using the
NSProcess info API.
So the first thing
you are going to want
to do is get the
NSProcess info instance
by calling the process
info factory method.
Next you are going to want
to call the API perform
expiring activity with reason,
and I am going to take you
through how to set up this call.
The first parameter
you are going to pass
to this method is a
very short string.
This short string is for you,
not for the operating system.
This tells you what
you are doing inside
of the protected task.
The next thing you
are going to pass
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The next thing you
are going to pass
to this API is a
call-back block,
and the call-back block is going
to be used in one of two ways.
And let me go through
the first way with you.
First, it's going to be
called if we were able
to acquire a background task
assertion on your behalf
with expired set to false.
This means to you
that it's safe for you
to perform some sensitive
task like serializing data
that you don't want
to be interrupted.
Now, since a task
assertion cannot be taken
out indefinitely, when the task
assertion is about to expire,
it's on the cusp
of being let go,
the operating system is going
to call your call-back again,
this time with expired
set to true.
This means that you need to
cancel whatever task you have
in flight and prepare
to be suspended.
Now, when you exit the
block, we are going
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, when you exit the
block, we are going
to release the task
assertion on your behalf.
There's nothing else
that you need to do.
So the second way that this
can work is that we were unable
to acquire a task
assertion for you at all.
What we are going to do in
this case is we are going
to immediately call
your call-back block
with expired set to true.
You probably don't want
to take an exclusive lock
in the shared container
at this point
because if you get suspended,
nothing is protecting you.
At this point, you
want to clean up
and get ready to be suspended.
You only have a few brief
seconds to make sure
to clean everything up.
So that's a complex topic, but
I think there are three things
that you need to come away with
this talk about task assertions.
The first is that
they're released
when your code exits
the call-back block.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
when your code exits
the call-back block.
We grab one at the beginning,
and if everything's successful,
we set expired to false,
and then we hold on to
that task assertion for you
until your code exits
that block scope.
The next thing to remember is
that there is potentially
re-enter and execution
of your call-back
for the purposes
of notifying you
about expiration.
Again, we can call your
call-back block again
with expired set to true to
give you a hint that, hey,
this task assertion
is about to expire
and it's time for
you to clean up.
And then finally,
task assertions are not always
available for your extension.
Sometimes you will call and
expired will be immediately set
to true and your call-back
block will be executed that way
and you will not get a chance to
perform some critical operation.
You need to be prepared
to deal with that
because it will happen.
Next, these task assertions only
protect the code in the scope
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Next, these task assertions only
protect the code in the scope
of that block, so they
are generally used
for very simple things,
like quick serialization,
something that's synchronous.
What do you do if you
need to coordinate
with work on another queue?
Because you remember that the
task assertions are scoped
to that call-back block.
Well, a call-back
block must synchronize
with protected work
on other queues.
So for instance, if you have
something on the main queue
that you wish to protect,
you need to make sure
that that call-back block does
not exit the original protected
block scope before that
work has completed.
So here's an example
of how not to do it.
By dispatch asyncing to the main
queue from within the block,
execution will exit
that block's scope,
possibly before perform
interrupt will work
has completed.
This is pretty bad.
So in this case,
your block needs
to synchronize via dispatch
sync or dispatch semaphores
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to synchronize via dispatch
sync or dispatch semaphores
or whatever method you
choose with the main queue.
And remember that this work
that you are dispatching
to another queue -- it
doesn't necessarily have
to be the main queue --
needs to be cancelable
because we might call back your
block with expired set to true,
and at this point,
you will need to clean
up whatever you are doing, exit,
go back to the block, drop out,
and release the task assertion.
And it's actually safe
for you to do this
because this call-back
block is executing
on a private system queue.
You are not going to dead-lock
because we are not going
to call back into it.
Now, that was a lot.
I understand.
Moving along here.
In our new multitasking world,
we can now run into situations
where your extension and the
containing application are
running simultaneously.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
running simultaneously.
And this means that if
something happens inside
of your containing app that
changes your model data state,
potentially your extension
could be out of sync
with the model state,
and this is bad.
So one way, one thing you
can use to keep everyone
on the same page is the
Darwin Notification Center.
Now, the API is similar to
the NSNotification Center,
but it's a lot simpler, and
there's a smaller number
of use cases for
which it is applicable
to your containing
app and its extension.
For example today, we are
going to show you how to use it
to hint your extension
to reload the model.
And this is how you do it.
First, inside of that containing
app, you are going to want
to get an instance of the
Darwin Notification Center,
and you will do this by
calling CFNotificationCenter,
get Darwin notify Center.
Then you are going to pass this
notification center instance
to CFNotificationCenter
post notification.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to CFNotificationCenter
post notification.
The next thing you are going to
want to do is to pick a string.
This string is going to
represent your notification,
and you are going to use
it in your containing app
and any extensions with which
to observe this notification.
And then you want to pass true.
In your extension, again,
you are going to want
to get an instance of the
Darwin Notification Center,
and you are going
to want to pass this
to CFNotificationCenter
at observer.
This allows you to observe
for this notification.
The next thing you are going
to want to do is you are going
to want to pass a
call-back block.
This is the call-back
block which is executed
when the system notices
that this notification
has been emitted.
Then you want to pass
the short string.
And finally, deliver
immediately.
This makes sure that
everything stays up-to-date
and the model inside of
your extension is reloaded
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and the model inside of
your extension is reloaded
when the containing app
passes it this hint.
Now, remember I am calling it a
hint so you probably don't want
to use this for coordinating
locks between your extension
and your containing app because
it's not always guaranteed
that your extension
is going to be
around to receive
these notifications.
So that's how to keep your
data model up-to-date.
How do you keep your
widgets visual
representation up-to-date?
And next I would like
to tell you about that.
We are going to use something
called background refresh.
And background refresh is
a way that the system works
with your widget to
keep it up-to-date,
to keep the visual
representation of it up-to-date.
So again, we are going back
to the Notification Center,
and here you can see
our stocks widget.
The stocks widget, again,
shows the latest stock prices.
What happens if, in the course
of the day, Apple stock changes?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
What happens if, in the course
of the day, Apple stock changes?
Well, this notification
will probably be emitted
to your phone, at which point
the containing app will probably
be expected to do
something with it.
And this will probably change
the visual representation
of your widget.
Unfortunately, we will notice
here that the widget is now
out of sync with
the stock price.
It's showing $130.12.
It should be $132.12.
So how do we fix this?
How do we make sure that
our visual representation is
always up-to-date?
Well, for starters,
the system is going
to opportunistically
refresh this content.
It's going to talk to the
widgets and see if they need
to be updated without
having to pull
down the Notification Center.
Each Today Widget view
controller conforms
to NC widget providing.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to NC widget providing.
And if the widget's view
controller implements the update
delegate method, it gets to
participate in this system,
and I will show you
how to do that now.
The first thing we are
going to do is we are going
to add widget perform update
with completion handler
to our Today Widget view
controller that conforms
to NC widget providing.
As a first step, we are
going to refresh the model,
and this is going to
do one thing for us,
one very important thing.
It's going to tell us whether
or not there is a change
in the model that is going
to materially affect the
widget's visual representation.
If it did, then we're going
to tell the view hierarchy
that it needs to redraw.
And finally, inside
of it, we are going
to take the completion
handler that was passed to us,
and if there was
a visual change,
we are going to pass new data.
If there was no material
change that was going
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If there was no material
change that was going
to affect the visual
representation of our widget,
then we are going
to pass no data,
and this lets our
widget be a good citizen
in the Notification Center
because at this point,
we are hinting the operating
system that it doesn't need
to expend any resources
to update the visual
representation
of our Today Widget.
Since we've done this, our
widget has been updated,
and you'll notice that the
stock price is correct.
Next, networking.
Extensions are ephemeral
objects,
really ephemeral processes.
They come and they go.
Remember, I was talking
about the user swiping down
and then quickly swiping up.
And this can play havoc with
things that take a while
to work, like networking
sessions.
And widgets are expected to
call out to cloud services
and other things on the network
in order to process data.
So how do we deal with this?
Well, I'd recommend
that you take a look
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Well, I'd recommend
that you take a look
at using NSURLSession
background sessions.
And what are these?
These are tasks, networking
tasks, that are performed
on your behalf by the system.
The updates and events are
delivered to your extension
for as long as it stays active.
But if your extension
gets suspended or killed
or for some reason
hangs up the phone
with this critical system --
or with this system component,
then the containing
application takes over error
and event handling
on its behalf.
And I will show you
how to set that up.
Inside of your extension
you are going to want
to first create an NSURLSession
configuration background session
configuration with
identifier object.
Or sorry, use the method.
And, again, following in the
pattern, you are going to want
to create an identifier
that's going to be used
by the extension and
the containing app
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
by the extension and
the containing app
which are participating in
this background session.
In our case, we are using
com.example.my downloadsession.
The next thing you
are going to want
to do is set the shared
container identifier
on the session configuration.
Otherwise, you are
going to have a lot
of fun debugging while all our
sessions come back as invalid
as soon as you start a task.
At least I know I did.
Next, you are going to want
to create an NSURLSession,
passing this session
configuration
to the initializer.
Now, you will notice that we
are using the delegate form
with the initializer because
the completion handler form
of the initializer
is not valid for use
with background URL sessions.
Next, for the sake of
the example, we are going
to download the apple.com
website.
We are going to create
the task and resume it.
And now our extension has
successfully created an
NSURLSession in the background
and handed it off to the system,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
NSURLSession in the background
and handed it off to the system,
and the system will begin to
deliver events to the extension.
Next, inside of our
containing app, as I was talking
about before, we need to prepare
it for any eventualities,
for cases where the extension
was suspended or killed
or otherwise dismissed and can
no longer handle the events
coming from the system
networking component.
We do this by adding
application, handle events
for background URL session to
our UI application delegate.
The first step we want to take
and the step I am taking here
again, for the sake of example,
is to make sure the
identifier is the identifier
that we expected.
The system is going to pass
in the identifier associated
with the NSURLSession for the
events that you are being asked
to handle in the
application delegate.
Again, just like in the
extension, we are going to set
up an NSURLSession
configuration item
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
up an NSURLSession
configuration item
by calling the background
session configuration
with identifier factory method.
And we need to set up the shared
container identifier again.
And then we need to
call NSURLSession,
passing this shared
configuration, and again,
using the delegate
variant of the initializer.
And then last but not least --
and this is very important --
you'll miss it if
you don't look --
is we are going to store
this completion handler back.
We are going to save it
back in our properties
for when we're completed,
when we are done handling
events for this URL session.
And you might ask,
how do you know
when you are done
handling events?
Well, I will show you.
In our NSURLSession delegate,
we need to add this method.
URL session did finish events
for background URL session.
It will be called when
your containing app,
or in this case the delegate
that your containing app
dutifully appointed to take care
of these events, is done
processing all the URL session
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of these events, is done
processing all the URL session
events from the background
URL session.
And the first thing we are going
to do is get the session
configuration object,
and then we are going
to look to make sure
that the session configuration's
identifier matches the
identifier that we expect.
Then we are going to call
this completion handler
that we saved earlier
and release our reference
to the session.
And at this point, we are done.
We have successfully handled
any of the events that have come
to our containing app
from the system component
which was previously
started by the extension.
Now, you can use this
technique for any
of your other extension
types, not just Today Widgets.
It's a really great technique.
But one place where
you can't use it is
for your Watch OS 1 extensions.
Instead, we would prefer
that you use background
task assertions
to protect these small tasks,
and you can find out more
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to protect these small tasks,
and you can find out more
about it by going to
WatchKit Tips and Tricks
in the Presidio tomorrow
at 10 a.m., given to you
by Jake Behrens, also
known as the man in plaid.
Next, when you are doing network
transactions, it's inevitable
that you are going to be
challenged for credentials.
How do you safely and securely
protect your users' privacy
by sharing these secrets
between your containing app
and its extensions?
We recommend that you use
Keychain access groups
for this, as shown here.
You set this up inside of the
capabilities for your extension
and for your containing app.
And then, using the
identifier that you set
up in your containing
app and its extensions,
you simply pass this identifier
as the value for the k --
access group key whenever you
add items to the Keychain.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
access group key whenever you
add items to the Keychain.
As shown here.
The next thing to
tell you about this is
that there's automatic
search behavior
for query API using
the Keychain API.
So sec item update.
Sec item delete.
And sec item copy matching all
automatically do the right thing
by searching every
accessible Keychain for you
so you don't have to pass the
Keychain access group identifier
to these APIs.
It's just a tip to remember.
And you can find out
more about these APIs
by watching the Security
and Your Apps video.
In summary, today Sophia
told you all about Action
and Share extensions, about
what they were meant to do
and how best to use
them to share data
with your app and the web.
She also showed you how
to use NS item provider
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
She also showed you how
to use NS item provider
to its fullest possible
extent and how
to make it do the hard work
of sharing data types for you.
She also showed you how to make
your host application a great
environment for Action and Share
extensions, and you know what?
This increases the
value of your host app.
Then I showed you how to enhance
your Today Widget by making sure
that your model objects and its
visual representation always
stayed fresh and up-to-date.
And then we showed
you a collection
of general best practices, like
how to securely share secrets
and guard your customers'
privacy.
There are the related
sessions I referenced earlier,
the WatchKit Tips and Tricks
one tomorrow, and Security
and Your Apps that you can view.
And for more information,
we'd recommend that you go
to the App Extension
Programming Guide or talk
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to the App Extension
Programming Guide or talk
to our Evangelist, Curt Rothert.
Thanks.
[ Applause ]