WWDC2016 Session 221

Transcript

[ Music ]
[ Applause ]
>> Welcome to Optimizing
On-Demand Resources.
I'm Bill Bumgarner with
the tvOS Engineering Team.
So in this session, well,
in the, in last year's WWDC
and in the developer
kitchens throughout the year,
we've covered really how
to use on-demand resources.
In this session, we
really want to focus
on how you optimize the use
of them, and in particular,
how you polish the user
experience to really make
for a fabulous user experience.
So we're going to look
at a basic overview
of some of the motivations.
How you assign tags, the use of
the API's, and then we're going
How you assign tags, the use of
the API's, and then we're going
to get into how you
optimize that first launch
and how you optimize the
ongoing user experience as well
as we'll look at optimizing
app updates, and we will get
into some of the
implementation details.
So why? Why did we do
this on-demand resources?
Well, if we look at a
traditional application,
it's composed of an application
binary and a bunch of resources,
and together these
make your app bundle.
This is what gets mastered,
uploaded to the store,
and this is what your
customers download and install,
and over time, they'll download
and install a bunch
of applications.
But if we look at the use
patterns of these applications,
what we notice is that only some
of the resources are used a lot.
Some of the resources
may have been used once
at the tutorial level
or something like that,
and this ends up eating
up a lot of disk space.
And it also means the
user has to kind of think
about what they want to keep and
what they don't want to keep,
and we don't really
want to make our users
into system administrators.
So with an on-demand resources
app, what we're really trying
to optimize is optimize
that resource usage
up around what is being used,
and make sure it's available
before the user actually notices
it needs to be downloaded.
So we take your traditional app,
and we divide those resources
up into the bundled resources
and the on-demand resources
that are not actually
on the system
when the app is installed
necessarily.
Now, there was some
misconceptions in the last year
about the size of
tvOS applications.
There was a, this notion
that they were limited
to 200 megabytes.
That's not actually true.
On tvOS, the main app bundle
is 200 megabytes, and iOS,
it can be up to 4 gigabytes,
however, in both cases,
they can have up to 20 gigabytes
of on-demand resources.
So on-demand resources.
They provide dynamically
loaded content that's available
They provide dynamically
loaded content that's available
on demand or can be downloaded
when the app's installed.
It's hosted on the App Store,
including hosting
across versions.
So upgrades aren't a problem.
Obviously, if you have
a user that sticks
on an old version,
it'll still work.
These are downloadable during
application installation.
They're also downloadable
during runtime by your request,
and you can control the priority
with which they're downloaded,
and you can shuffle
that priority
around as the user may change
their mind and move around
and do whatever they want to do.
As well, all of this works
together with the system
to provide intelligent
content caching as well
as intelligent purging.
Again, let's get the
user out of the game
of administrating their systems.
So the benefits to your app
are small or main app bundle,
which means it's fast
or initial download.
What it means it's a
faster period of time
between which they
click that buy button
and they're using your program.
As well, you get a lot richer
app content, up to 20 gigs.
As well, you get a lot richer
app content, up to 20 gigs.
I mean, that's a lot of space.
And you can, there can be more
apps installed on the system.
They're ready to run,
and it reduces the need
to manage that storage.
It also means that, you know,
they take a bunch of pictures,
and some of the on-demand
might get flushed out,
and that's all automatic.
So how do we do this?
How do we adopt this?
Well, the first thing you have
to do is you have to assign tags
to all those resources,
and you do that by looking
within your application,
looking at all those resources,
and figuring out the roles each
resource plays within your app,
and when you need them.
And then you go into
Xcode, and you assign tags.
Now tags are nothing magical.
They're just strings.
Just any old string you
want, Level 1, whatever,
and they can be applied
to a single asset
or a single resource,
a sound file,
a texture, an image, whatever.
Pure data, or they can be
assigned to entire folders.
Pure data, or they can be
assigned to entire folders.
As well, any given resource
can have multiple tags
because it might
play multiple roles.
So let's go back and let's look
at our GreatGame, and let's look
at those resources,
and, specifically,
let's break those
resources out by role.
So in this,
it's a straightforward
level-based application,
and it has resources
that are required always.
These are the ones that,
like, your launch screen,
your splash screen,
maybe the setting screen.
That kind of thing.
And then it has resources that
are in the role of supplying
for each level as well as maybe
something for a purchasable item
or an in-app purchase.
And to tag these, it's
pretty straightforward.
Just give them the same
name as their role.
So when we do these,
when we look at these,
what's the strategy for
tagging these things?
Well, only put in the
main bundle the resources
that are absolutely
positively needed
by the application all the time.
Your loading screen.
Your splash screen.
That kind of thing.
And then you apply tags to
everything else that's there.
And each tag can be applied
to up to 512 megabyte
of assets or resources.
However, we really
recommend that you stick
around that 64-megabyte
limit simply
because that makes the
downloads that much faster
and less perceptible
to the user.
And, again, you can have more
than one tag per resource,
and it, whenever you use one
of the tags, it will pull
down all the resources
as appropriate.
So now that we've got
everything organized and tagged,
let's look at the runtime side.
Within the runtime, we tried to
make the API very simple, and,
in fact, it's only one class.
There's the bundle
resource request class.
Now you create an instance of
this to manage all the access
to your on-demand resources.
It's created with a
tag or a set of tags,
and it has some other
options for managing it.
You use it to begin and end
accessing to those resources.
You use it to begin and end
accessing to those resources.
Begin accessing is what triggers
a download if necessary,
and end accessing is what tells
the system, hey, I'm done.
And on this object, you
can also set the priority.
If you have a particularly
large download
or a particularly
slow connection,
you can track progress, and
there's also the possibility
of an error, which we'll
talk about in a minute.
One of the interesting things
about this class is each
instance is one shot.
They are very lightweight,
very cheap to create.
So that means when
you create one,
and you call begin
accessing on it
after you've called end
accessing, that object's done.
Create a new one.
And one concept that we find
is very important to take
to heart is that the request
is decoupled from access.
So you decouple when
you make that request
from when you're going to
use the resource, and we can,
we'll cover this in
the predictive loading.
So we can predict what
the user's going to do
to make sure they never
see those loading screens.
to make sure they never
see those loading screens.
So looking at the actual code,
it's really straightforward
to initialize a bundle
resource request.
Just give it a set of tags,
and you have your request.
If you want to begin the
accessing those tags,
you call begin accessing, and
it has a completion handler,
and that completion handler
will be called with an error
if there is an error, or
it will be called no error,
and you're resources
are available.
To get at the resources,
you use the bundle API.
So you grab the NS bundle
instance, I'm sorry,
the bundle instance [inaudible]
renaming, from the request,
and you just use the normal
resource request methods
on bundle to get a hold of that.
And once you're done, it's very
important to call end accessing.
This tells the system that
you're done with that resource.
Now it's very important to note
that that doesn't mean the
system's actually going
to delete the resource.
Our systems are very lazy.
They don't want to
do any extra work,
and deleting stuff's extra work.
So when you're loading
resources,
So when you're loading
resources,
you can control the priority.
Like, say you're moving
through your, a game, and,
and the users change their mind,
and you were downloading
this level over here,
now you need to download
Level 5.
Well, you start at
the begin access.
You can go, and you can
change the loading priority
to bump the priority
on the Level 5 stuff
and decrease the
priority on Level 3
if you think they might go
back, or you can end it.
It's just a value
from zero to one,
but there is the
special urgent priority.
There will be times when the
player has decided to go off
in a direction that you couldn't
possibly have predicted,
and you need to just
download everything right now.
And in this case, this special
high urgency loading priority
can be used.
It suspends all other download,
and it also maximizes
the throughput.
So there's no network
throttling,
and it also maximizes CPU usage
dedicated to that download.
Finally, there's
conditional requests.
Now a conditional request
can be used to check to see
Now a conditional request
can be used to check to see
if the resources has
already been downloaded.
So if you remember when I
said end accessing doesn't
necessarily delete
resources, well,
the player's been
playing a game.
They've gone through
Level 1, 2, and 3.
You've ended accessing
to Level 1, 2, and 3.
They quit the game.
They've gone off and
done something else.
They come back, relaunch
the game,
and they want to replay Level 1.
Well, you can use conditionally
at, or say they want
to select between levels.
You can use conditionally
begin accessing
to check what levels
are already downloaded,
and give them indication
of what's already
available to play.
Or if they dive into a
level, and you've broken
up your resources by
role within the level,
maybe you optimize the
first part of the level
to only showing the trees and
bushes and enemies that happen
to be on disk at that time
while you download the rest
of it in the background.
So all of this is
about you being able
to avoid loading screens
whenever possible.
And if the items are
already downloaded,
this works exactly
like begin accessing.
this works exactly
like begin accessing.
And as well, as always,
call end accessing,
even if you got the
callback, and it was false,
and you decided you didn't
want to trigger it download,
always call end accessing.
So now you have a
working application,
but let's look at
that first launch.
And let's look at a timeline
in particular, and we're going
to go with a timeline from
the moment the user buys the
application in the store, it
gets downloaded, it's installed,
and then the first launch
happens, and what do we do.
The first thing we do, we
begin accessing Level 1,
which triggers a download,
and then the player can play
the game, and then they get
to Level 2, and we
do begin accessing,
and it downloads, and they wait.
They play, and we
keep doing this.
Level 3, download, wait, play.
And even with the purchasable
items, in-app purchases.
Download, wait, play.
That's not a good
user experience.
Making the user constantly
look at loading screens, no.
We're not going to do that.
So the first thing we're
going to do is we're going
to take advantage of features
and on-demand resources
that are built in to
optimize that first launch
from the get go, and the
first thing we're going
to use is initial install tags.
And the next thing we'll
use are the pre-fetch tags.
And with these in place, then
that Level 1 will be downloaded
and installed when
the app is purchased,
and Level 2 will be downloaded
and installed immediately after,
and hopefully the user can
dive in and just start playing.
And then we'll look at
predictive download,
but first let's take
a step and look
at how we can configure
the initial pre-fetch.
So initial install tags.
These are tags that are marked
up in Xcode to be downloaded
These are tags that are marked
up in Xcode to be downloaded
as a part of your
application installation.
You can have up to 2 gigabytes
with these resources,
which is a lot.
It's part of the size shown in
the App Store, and, in fact,
when the little download
progress indicator goes,
that reflects the initial
install tags as well.
The pre-fetch tags, they're
a little bit different,
but you can have as many
pre-fetch that's as you want
up to 4 gigabytes minus the
size of the initial install.
And it follows an order
specified in Xcode,
and the pre-fetch tags
are downloaded immediately
after the initial, and they
don't prevent app launch.
So the user will be able
to dive into the game
and start playing even though
the pre-fetch stuff's coming
down in the background.
And in Xcode, this user
interface looks like this.
This is the resource
tags inspector inside
of your target editor
for your application.
It's got three sections:
The initial, the pre-fetch,
and the download
only on demand tags.
You move stuff in the initial.
These are the ones that will
be bundled with your app
and installed at the same time.
The pre-fetch, these
will be downloaded
after in the order you see on
the screen, and then, finally,
the download only on
demand are the ones
who will only be downloaded
when you begin accessing
on those tags.
So going back to our timeline.
We talked about predictive
loading very briefly,
but what does that really mean?
Well, we got our initial.
We got our pre-fetched,
and we're still making
the user wait at Level 3.
So, instead, if we simply begin
accessing the Level 3 tags
somewhere in Level 1 or 2, it'll
probably be downloaded and ready
to play by the time the
player gets to there.
As well with purchasable items,
if you have a particular point
in your app, game, or whatever,
that you think it's likely
or you hope the player's going
to go and do an in-app purchase,
go ahead and begin
accessing there.
You don't give them access
until you got the receipt,
but at least it'll be there,
and there'll be no waiting.
but at least it'll be there,
and there'll be no waiting.
Now we've got one big green
timeline and a very happy user.
We've talked about
this level-based game,
which is a very linear
access pattern.
It's very convenient for
making beautiful slides.
It's not the real world.
In a linear access
pattern, the majority
of the assets are
going to be used.
They're very much going
to be used in order.
Your tag size isn't
really that critical
because you can always stay
well ahead of the user in terms
of getting accessing, but
the issue is that, of course,
nothing's ever linear.
And in particular, a lot
of times we'll have an app
that has a very random
access pattern,
and the player may go
all over the place,
or there may be things that are
shared amongst levels, or they,
you know, they may select
certain configurations,
or they may buy certain in-app
purchases, and in this case,
the goal is really to try to
predict as much as possible
to try to pull down stuff
before the user needs it,
to try to pull down stuff
before the user needs it,
but in the case where you have
to pull it down really on demand
at that moment, stick
to small tag groups,
and that will make
very fast downloads.
And you can download
sets of tags proactively.
It's OK to go and kind of
just guess at what's going
to be needed and let them
be, put down on disk because,
of course, we have this
intelligent caching mechanism
that's working in the
background to make sure
that the right things
get deleted
if there is disk pressure.
And, again, end accessing
doesn't mean deletion.
So if you go off, and you
predictively download a bunch
of stuff, and then you don't
ever need it, well, that's OK.
Just end accessing, and
it'll probably still be there
when you do need it
whenever in the future.
Now there's another pattern,
which is kind of in between,
and that's your explorative
access pattern,
and this is the one where it's,
you know, the, that you wonder
from village to village on
quests and things like that.
And in this case, there
is limited prediction.
Many possibilities will not
be used, but you often are
at a branch, and when
you're at the branch,
at a branch, and when
you're at the branch,
you can load a subset
of your tags.
Oh, the user make a
left, make a right.
So I'll load the left tag and
the right tag, and then as soon
as the user makes the
decision, expresses their intent
to go right into accessing on
left, let that download stop,
focus on the right, and start
predicting a one step ahead.
So now we have this working app.
We've got a great user
experience, and that's all well
and good, but let's look at some
of the implementation details
that are going on behind the
scenes that you can be aware
of to optimize this
experience even further.
And in particular, as I said,
the app bundles, they're limited
to 4 gigs on iOS, 200 megabytes
on tvOS, but you can have
up to 20 gigabytes of on-demand
resources, and of those,
up to 2 gigs will be downloaded
and installed with the app,
and up to 4 gigs will be
pre-fetched minus those 2 gigs
and up to 4 gigs will be
pre-fetched minus those 2 gigs
or up to 2 gigs of install.
There's some additional
numbers to keep in mind.
You can have up to 2 gigabytes
of resources active
at any one time.
So you go and you begin
accessing on up to 2 gigabytes
of tags, and those
will be downloaded,
and they'll be made
available, and that's great.
When you go over the 2
gigabytes, what happens is
that the begin accessing method,
that callback gets an NS error
that indicates that you are
out of tagged resource space.
You need to go in to end
accessing on some current set
of tags to free up some space
to allow more to be accessed.
Now, reiterating this point
because there was another point
of confusion, if you have 2 gigs
of tags that are pinned down,
and you want to access more,
and you go into accessing on,
on 500 meg of them, and pin
another 250, that 500 meg
of resources are probably
not going to be deleted.
They'll be around and available,
but it just lets the system know
if things get dire, it
can go and clean them up.
if things get dire, it
can go and clean them up.
Any one tag, again,
up to 512 meg,
try to stick to 64
megs or lower.
And you can have up to
1,000 total asset packs.
What the heck's an asset pack?
Haven't mentioned
that word yet at all.
Well, an asset pack is fallout
from the Xcode build system.
It's the way your application
is built and mastered.
It's the way the on-demand
resources are compiled together
and managed by the store.
If we look at our great
game, in this case,
a role-playing game,
it doesn't matter.
We have our tags, and as is
very typical, resources tend
to be used more than once.
Things get used from
Level 1 to Level 2.
Enemies become friends.
That kind of thing.
So we have these
two resources here
that then get used on Level 2.
So they're tagged with Level
1 tags and Level 2 tags.
So our tag set looks like this.
We've got four resources with
one tag and two resources
with two tags, and while
we only have four tags,
this ends up with
six asset packs.
Now if you think through
like a random access game,
this could be a stumbling block.
If you have a lot resources
that are shared across lots
of different roles
such that many
of those resources
have five, 10, 15 tags,
then the cross product of all
those could end up exceeding
that 1,000 tag or
1,000 asset pack limit,
and that is something
to be aware of.
So in the life cycle of any game
or any application, of course,
you're going to have
application updates.
You want to improve
that user experience,
get the users coming back.
And on-demand resources
have been optimized
for application updates as well.
A little bit, maybe
a little surprising,
A little bit, maybe
a little surprising,
but if you think it
through, it makes sense.
In particular, we start out
with our version 1.0 game,
and we have a bunch of resources
in that game, bunch of tag ones
and some main bundle ones, and
when we ship version 2, well,
we've modified something
in the main bundle.
We may have added some
resources to Level 1,
modified a couple
things on Level 2.
We added a whole new level,
and that's all well and good.
So what happens across
the updates?
So the first thing is
when you update resources,
update tagged resources, nothing
is redownloaded automatically.
It's redownloaded when
it's first accessed.
We don't want to
redownload the tutorial level
when the user's way beyond that.
Any unchanged resources
will just stay on disk,
and they can be accessed
without download.
New resources, they'll be
downloaded when accessed.
So, really, once again, it's
the system taking a very lazy
approach to this.
In this case, because we can't
predict what changes you've made
to the app that are
going to be required
by whatever state the player or
the user is left your app in,
we're going to leave
it up to you
to trigger the begin accessing
to trigger the updates
and pull down the new stuff.
And, in fact, on first
launch, you might want to go
and begin accessing a couple of
your changed things to make sure
that they are made available
before the user notices.
So best practices for this.
Just avoid making
unnecessary modifications
to tagged resources, including
things, like, you know,
we had a situation where
somebody made a spelling change
and was surprised
when everything got redownloaded
the first time they accessed it.
If you change one
resource in an asset pack,
it's going to trigger
the download
of the whole asset
pack, and this is a,
of the whole asset
pack, and this is a,
just an implementation detail.
So keep that in mind.
So what you can do instead
is, like, say, for example,
in the case where we added
a couple of extra resources
to Level 1, we can make
Level 1 update one tag,
and then begin accessing on
both of those, pull them down,
and when they're both available,
then allow the user
to play Level 1.
Keep those tags consistent,
and from the beginning
you really want to design
with a separation of
updateable content
versus static content in mind.
And all that means is, you
know, where you may have a tag
for one single role in your
application, maybe you want
to divide that tag into multiple
tags where it's something
that you know will
probably never, ever change
and a handful that will.
Now how does all this contribute
to the intelligent
content caching?
So on tvOS, one of
the goals of the OS is
So on tvOS, one of
the goals of the OS is
to never have the user
be aware of this usage.
Never have to go
and delete anything.
Never even have to
think about it.
And as a part of that, there's
this whole cache management
system and automatic
purging system.
And the system's going to
purge resources from the disk
when there's a dire
need for disk space,
and there's multiple
levels to it, and it starts
out the lowest priority,
and we'll clean up caches
and whatever, and
then it'll move
up to higher and
higher priority.
And so, again, pounding on
this point, it's important
to ending access to a resource
when you're done with it,
and that does not mean deletion.
There's a number of variables
that inform the system
about purge order.
Obviously, least recently used
go away first, to a degree.
You also have control over
the preservation priority.
This preservation
priority can be a sign
to the bundled resource request,
and it's a number from zero
to one that just
determines when,
what order the system
will delete stuff.
It's isolated to
your application.
So there's no cheating.
It doesn't help you to
set everything to one.
It just means we're going
to blow everything away
if it gets that bad.
And if your application's
running,
it's going to be the
last one the system tries
to purge resources from.
And this is very important.
Don't use temp or caches.
I mean, obviously use them
if you need some temporary stuff
or, you know, per session cache,
but because we can't know
anything about the structure
of the data in a temporary
cache, the system's going
to treat those are purgable
at a pretty low priority.
They're going to be purged
first, and when they're purged,
they're purged in
their entirety.
they're purged in
their entirety.
So, finally, in conclusion,
use on-demand resources.
In particular on tvOS, the use
of on-demand resources
really provides
for a much more optimal
user experience.
It leverages that always
on network connection.
On iOS, things can be
a little bit trickier,
but there's a number of ways
that it can be used
very successfully.
It will give you that
smaller app bundle,
which gives you a much faster
customer acquirement time
from store to your customer
playing your game using your
program doing whatever
with your content.
You get this richer app content.
You now have 20 gigabytes
of space to play
with to put together
whatever you want.
And it also means for the user,
they don't have to
think about it.
They can just install as many
applications as they want.
They can just install as many
applications as they want.
They don't have a barrier to
entry there, and they don't have
to think about the storage.
So that's on-demand
resources optimization.
For more information, there's a
whole web page devoted to this.
There was also a number
of related sessions
earlier in the week.
Encourage you to review them.
Thank you.