Using On-Demand Resources allows you to create smaller app bundles, enable faster downloads, and add more content than ever before on iOS and tvOS. Learn the recommended approach for delivering apps packaged with On-Demand Resources. Explore strategies for pre-fetching content and understand how to optimize the first-launch experience.
[ Music ]
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
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
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.
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.
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.
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.
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,
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
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.
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
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.
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,
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,
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
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.
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,
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,
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
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.
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 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.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.