Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[Applause]
>> STEVE LEWALLEN:
Well, good afternoon!
And welcome to today's session
introducing On Demand Resources.
My name is Steve Lewallen.
So what do we have
for you today?
Well, we're going to begin
by giving you an overview
of On Demand Resources,
or ODR, and we're going
to compare an application built
with ODR to one built without.
We will follow that by listing
all the features and benefits
to you, the developer, as well
as to users of ODR applications.
Then we will deep dive into
a bit of the details of ODR,
how an ODR application is
structured, how you build one.
We will follow that up by
giving you a demo using Xcode
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We will follow that up by
giving you a demo using Xcode
and the new ODR API we
have added to Foundation.
And finally, we will wrap
things up with a segment
on best practices in using
and building the
optimal ODR application.
So let's get started.
So to understand where
we are going with ODR,
we need to understand
where we are today
with the traditional
application.
Now, this application will
have an executable segment.
This is your compiled
Swift, Objective-C,
C++ as well as some base
resources, and the levels
in the game, for example,
if this is a game.
When you, the developer,
are happy with this game
or application, you
upload it to the store,
and when the user comes
along and buys the app
or game they get the
entire thing and down
to the device it goes.
And there it is not alone.
Here we have all the other
compelling applications
and games that you,
the developer,
are writing for all
of these users.
In fact they are so compelling
that they can't get enough
of them and literally
this means more
and more the case this
is literally true.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and more the case this
is literally true.
There just is not enough
space on the device.
So what are we to do?
Well, one thing we can do
is make the observation
that not every part of every
app is needed at any one time.
I will give an example.
I may be on Level 7 of a game.
I certainly don't need
Level 1 any longer
and I probably won't need
Level 30 for quite a while.
So it's with that
thought that we started
to begin to think about ODR.
So now let's look at
an ODR application.
In this case, we start
off with the same bits,
we haven't taken anything
away, but what we're going
to do is we are going to
tease apart the assets
for each level of that game.
And then we are going to upload
the entire thing to the store.
Now, when a customer comes
along and buys the app,
they get the executable
and the base resources
and perhaps the first
level in the game.
So no pun intended we are
already ahead of the game here
because they were able to arrive
at the first level of the game,
the time between buying the app
and playing that first level,
much more quickly
than if they had
to download the entire
app first and they had
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to download the entire
app first and they had
to use far less disk
space to do it.
Now, the user starts
playing this game
and the first level is in use.
And a well-designed ODR
application will anticipate
needing, say, the next
level in the game.
So it will download that.
The user continues to play,
they move on to Level 2,
and at this point, the
caching mechanism built
into ODR notices you are
no longer using Level 2,
well I'm just going to
make a note of that.
It's still there, we haven't
done anything with it,
but we're just going to
remember it's no longer needed.
So this continues until we
get to the point, perhaps,
where there is no more
space on the device
for the next level of the game.
We have looked at every other
possible place on the device
to free up resources and all
we have left is ODR content
from this game.
In this case, the caching
mechanism can step in again
and say, you know, I had
Level 1, I can free that up
and now the user can
continue on with the game
and play the next level.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this is the basic
idea behind ODR;
in the case of a game it's fast
to first play from buy to play
and it's always occupying a
smaller, more manageable amount
of space on the device.
So now let's talk about
all of the features of ODR
that we provide you,
the developers.
First of all, obviously this
is a dynamically loaded content
system and you have
probably used systems sort
of like this before, but we
have added some new wrinkles.
First of all we are
going to host the content
on the App Store right
along with your app.
Second of all, we can download
content during app install
as well as by request and
in fact we can automate,
we can automate this
content download at any time.
And finally, we also include an
intelligent caching mechanism
that I mentioned.
So, for example, we can free
up space to load something new.
And finally, a traditional app
maxes out at 4-gig submission
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And finally, a traditional app
maxes out at 4-gig submission
in the Store, but with
ODR your app can now be
up to 20 gigabytes.
Most of this, of
course, is ODR content.
All right.
So that's what's in it
for you, the developer,
but what's in it for the user?
Well, first of all, we can
improve the install experience.
A traditional DLC system, a game
written using such a technology,
provides an experience
something like this:
the user downloads the
app, they are all excited
about playing the game.
They launch it, but oh no,
the game now needs to go
and download new content.
With ODR we can make sure that
that content is on the device
when the app looks
like it is installed.
So the experience for
the user is much better.
Second, because we are
occupying a smaller
and more manageable space on
device, the footprint of the app
at any one time, we can have
more apps on the device ready
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
at any one time, we can have
more apps on the device ready
to go and more apps ready
to go at any one time
on the device is always a
good thing for the user.
And finally, for the
user, because, again,
there is more space and we
are keeping a lot of the app
or the game in the cloud on the
App Store, we can have levels
that are richer and more
expansive, for example.
And that is always a good
thing for the user as well.
Okay. So now let's dive into
some of the details about ODR.
First of all, it's an
elementive app thinning in iOS 9
and it's well integrated
with app slicing.
In case you missed the
details of app slicing earlier
in the conference,
app slicing is all
about tailoring the download of
an app to a particular device.
So let me give you an
example: You write an app
that targets many different
iPhone sizes and iPads.
A user with an iPhone comes
along and when they buy
that app, app slicing makes sure
that they only get the resources
you need for that phone.
In the past they
would have gotten all
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
In the past they
would have gotten all
of the assets including
those for an iPad.
So now when we combine app
slicing and we join it with ODR,
we double the benefits of both.
The footprint is
still even smaller.
We are getting to
first play even faster
and the constant
steady state of the app
on device is much smaller.
Okay. So what does
an ODR app look like?
How is it structured?
It's pretty similar to an
app today, but as you recall,
we teased apart the assets
for those levels in the game.
We call these asset packs.
And the rest of the app
remains your dot app.
Now, you group these together
using Xcode with simple tags.
For example, these are
all my assets in Level 1
of the game, for example.
It's pretty simple to set up.
And you can tag a single
file or a whole folder.
This whole folder is
Level 2, for example.
All right.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, what can you tag?
Well, pretty much anything
that can be in a dot app today.
For example, images,
sounds, data, scripts,
many games have scripts in them,
you can also have
in-app purchased content.
So now you can tie together
your in-app purchased receipts
with in-app purchased content
that you are actually
downloading via ODR,
and taking advantage of
all the other ODR API
to help manage that.
The only thing you can't have
is executable content: That's,
again, the compiled
Swift, Objective-C, C, C++.
Leave that in your dot app.
All right.
So where is this content hosted?
Well, I mentioned
one of the locations;
it's obviously the App Store.
We host it in the App Store and
we serve it on demand as needed,
but during development, Xcode
stands in for the App Store,
whether you are developing
against a device
or the simulator.
And it hosts it to your app
and delivers it on demand.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, another tool in the Xcode
tool box is the Xcode Server.
You can set this up in
your own department,
such that the app along with
the ODR content is available
on that server, for example,
your Q/A engineering
group to test your app.
And as you might expect,
TestFlight is fully
integrated with ODR.
And finally, if you
are deploying an app
into Enterprise, you can
actually host ODR content
on a web server inside of your
enterprise including behind a
secure login so that not just
anyone can get at that content.
So that's where we
host ODR content.
All right.
So how do you get started?
Well, as a developer,
the first thing you need
to do is take a look at all the
many assets you have in your app
and start to identify them.
You need to categorize them.
You use this, again, by tagging
them with simple strings.
These are all of the assets
that are in Level 1 of my game.
These are the assets
for Level 2 of my game.
And you know, there are some
shared assets between levels?
There they are.
So you can tag assets with
multiple tags in order
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So you can tag assets with
multiple tags in order
to indicate the sharing
and avoid duplication.
So this is part one of
getting ready to use ODR
and that was your
job as a developer.
Now, Xcode does its job because
it takes all this tagging
and groups these
into asset packs.
In this case, we ended up
with three asset packs.
We have the asset pack for Level
1, the asset pack for Level 2,
and we ended up with
a third asset pack
which is the shared assets
between those two levels,
again, avoiding duplication.
Okay. So Xcode has done its job.
Now, it's back to
you, the developer.
The first thing you need
to do is basically just
request the resources: Hey,
I need everything for Level 1,
and down come the two asset
packs, both the dedicated one
to Level 1, and then the
one that was sharing assets
between Level 1 and Level 2.
And because we brought down
that shared asset pack,
when we request Level
2, we get those assets
and we already have the
shared assets on the device.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we already have the
shared assets on the device.
So this is a high level overview
of what ODR is all about as well
as how to build an
ODR application.
Now, I would like to
invite on stage Tony Parker
to show us how it's done
in Xcode with a real API.
Tony.
[Applause]
>> TONY PARKER: Thanks, Steve.
So, again, my name is Tony
Parker, I'm the manager
of the Foundation team at Apple.
So Steve gave you
a basic overview
of how the ODR system works.
Now we are going to dive
in to the API that we use
in your application to
actually make those requests.
There is really just one piece
of API that you need to know.
It's a new class in Foundation
called NSBundleResourceRequest.
This class follows the
command design pattern.
So what that means is
you're going to set up one
of these objects with
a set of options,
including of course the set of
tags that you are interested
in using, and then you tell
it to begin its request.
So you can create as many of
these objects as you need.
That's because the system
will ref count the tags
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
That's because the system
will ref count the tags
under the hood.
So if you have several different
parts of your application
that use ODR and perhaps use
overlapping tags, you don't need
to create a manager
class to keep track
of which ones you
have in use at a time.
We will go ahead
and do that for you.
The most important point,
design point about this class is
that the request is decoupled
from the actual use
of the resources.
What this means is that all
of the APIs that you use today
in your application and
you are already familiar
with like NSBundle's
URLForResource,
NSData's dataWithContentsOfURL
or UIImage imageNamed,
all of those APIs
remain actually as is.
You just need to tell the
system in advance using one
of these request objects
that you are going
to need those resources
to be present.
This object forms a very
simple state machine.
So start off in init.
We move it to this
requested state
when we anticipate the
need for those resources.
And we're going to get back
a completion handler callback
which will tell us either the
resources are now available
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which will tell us either the
resources are now available
for you to use and you can
continue to use those APIs
that we just discussed,
or an error occurred.
Now, of course, when you are
using ODR there are a few errors
that can occur that we may
need to present to the user.
That could include, perhaps,
there is no network available
and we need it to
download the content,
or maybe there is not enough
disk space on the device even
after we tried purging
to hold the content.
So in any case, you need to
present that to the user.
Perhaps there is something they
can do to resolve the issue.
So if the resources were
available, then, as I said,
you can continue to use
them, and also important,
when you are done with those
resources, please tell us
about it, and there are
two mechanisms to do that.
The first is to call an
explicit API on this class
that tells us you are
finished with the content.
The second is to allow the
class to be deallocated,
allow the object
to be deallocated,
in which case we are
going to go ahead
and end the request
on your behalf.
So here is what the
basic methods look like.
First, an initializer, and
you can see it takes a set
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
First, an initializer, and
you can see it takes a set
of strings.
Those are the tags that you
are interested in requesting.
The method to begin
the request is called
beginAccessingResources
WithCompletionHander,
and you can see there's
the closure
that has the NSError argument.
And finally that method
that tells the system
you are finished,
the explicit
endAccessingResources call.
So next I would like to go
into a demo and show you some
of this API in action.
All right.
So here you have our demo app
today, it's called iTravel,
and iTravel is called iTravel
because it's a travel guide
that gives you all kinds
interesting information
about countries whose names
begin with the letter I.
As you can see, we support
two countries today:
There's Iceland and Italy.
Now, this kind of application
is a great candidate
for adopting On Demand
Resources.
And that is, the reason is
because when the
user buys this app,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because when the
user buys this app,
they are maybe not interested
in getting all the information
about both Iceland and
Italy or at least not both
at the same time so we
can make the install size
and the download size much
smaller by downloading
that content on demand.
So before we adopt
ODR I just want
to show you a brief
example of how you use this.
First I will go ahead
and visit Iceland.
You can see that I get a list
here of points of interest.
So I can pick one of these
and there is some high
quality pictures, perhaps,
or guide text, and
each of these points
of interest has more pictures.
So you can see that this could
add up to quite a bit of data.
And Italy, of course,
behaves the same way,
but with a different
set of content.
So let's look at how this
application is built.
We are going to spend
most of our time today
in just one class, it's called
the AlbumTableViewController.
This is the view controller
that controls this view
that we see right here with
the list of points of interest.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that we see right here with
the list of points of interest.
So let me show you how it works.
When we segue into
this view, we are going
to have this function
called loadAlbum,
and the argument is going to be
which album we are interested
in looking at, either
Italy or Iceland.
We set our title and we
call this helper function
called populateTable.
Now, here in the populateTable
function, we are going
to use NSBundle's URLForResource
to find a JSON file
which describes all of the
points of interest with pictures
to show what the captions are.
We are using NSData's
contentsOfURL method
to actually read that
JSON file from disk.
We are using NSJSONSerialization
to parse it.
We are setting up some more
detailed label text here,
and finally we ask the
tableView to reload itself,
and the data source
in the tableView is
using UIImage.imageNamed
to actually show that,
to fetch that picture.
So the important point here
is that as we adopt ODR
in this application, nothing
in this populateTable
function has to change.
So, again, all of the
APIs that you use today
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, again, all of the
APIs that you use today
that access contents of files
on disk or finds those files
like NSBundles, those
remain exactly the same.
So the first thing we are going
to do is actually add some
tags to our application.
So to do that, I will bring
up the inspector here,
and you can see that I have
already organized my application
to have a group called
Resources,
and folders that contain
some of my content.
So this one contains all of the
picture from Iceland, this one,
all of the pictures from
Italy, and these JSON files
that I was discussing.
So what I'm going
to do is go ahead
and select both the JSON file
for Iceland and the folder
and look on the right side
here in this inspector.
You see there is a new field,
On Demand Resource Tags,
so all I have to do
here is start typing
and we will tag our
content as Iceland.
And we will do the same
for our Italy content.
So next we just, so that,
what that does is, of course,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So next we just, so that,
what that does is, of course,
as Steve explained, tell Xcode
how to split up your content.
Now, the next part is to
tell the system at runtime
that when we anticipate the need
for that content
to be available.
So we will do that here
in this view controller.
So the first thing I'm going to
do is add an I var to my class
that holds the class that
we've been talking about,
NSBundleResourceRequest.
So what I'm doing is
taking advantage of the fact
that when this view
controller is torn down,
we maintain pretty tight
control over this object.
So we can control its lifetime
so when it's torn down we know
that we are done
with the request
and we can allow the
system to deallocate it
and that will tell the system
we are done with the content.
Next in our loadAlbum function
before we call populate table,
we are going to create the
request, we are passing
in the set of tags, in this
case it's a set of one tag
which is either Italy
or Iceland.
We call beginAccessingResources
WithCompletionHandler (without
space), and when
we get a result,
this closure will be called,
and it may have an error.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
this closure will be called,
and it may have an error.
So on the main thread, because
this completion handler will be
called on a non-main thread,
we are going to first check
to make sure that
the error is nil.
If it's non-nil, then
an error occurred
and as we discussed it's
important to present
that to the user, which I'm
doing in a helper function here.
Then we call the exact
same populateTable function
that we just went through.
So, again, nothing
in there changed.
Let's go ahead and run
the application again
and see how this looks.
So now, I'm going to visit
Iceland and we will see
that my content is here because
we have made a request for it,
and I can choose some of these
pictures or points of interest,
and all of my content
is available just
as if it had been part of the
application from the start.
Now, this is a good opportunity
to show you a new
debugging feature in Xcode
that can really help
you to understand
if you are using
these request objects
in the way you think you are.
And that's a new debug gauge.
So I'm going to bring
up the debug gauge view
here and choose disk.
And you'll notice in the middle
we have a new section called
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And you'll notice in the middle
we have a new section called
On Demand Resources.
It lists all of the tags that
are part of your application,
so Iceland and Italy, their
size and their current status.
You notice that Iceland
is listed as in use,
and that makes sense
because we are looking
at the Iceland content right
here and it remains in use
as I view some of
these pictures.
But if I go back
to the main table
of contents its status
is changed from in use
to downloaded, and
that's because again
that request object
was deallocated
and the system now knows we are
no longer using that content.
We didn't delete it off disk
immediately so if you go back
to Iceland the content
is available again
and the status changes
back to in use.
Let's see what happens
if I visit Italy now.
Now, you notice that it's going
to take some time to
show this content.
And the reason for
that is that the size
of the Italy tag is actually
much larger than the one
for Iceland; it's
130 megabytes here.
Now, there are two things
that are important to do here.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, there are two things
that are important to do here.
The first is to actually reduce
the size of the tagged content,
split it up into smaller
chunks, and we are going to go
into some more detail on the
best practices for that later,
but for now, what I want
to do is show you how you
can adopt progress reporting
in your application
in conjunction
with NSBundleResourceRequest
so that
when you are downloading
this content you can display
something to the user so they
understand what's going on.
So let's go back to our
AlbumTableViewController class.
So what we are going
to do is take advantage
of NSBundleResourceRequest
support for NSProgress,
which is a foundation class
you can use to report progress
and compose progress
across your application.
NSProgress supports key value
observing, so what we're going
to do is add this view
controller as an observer
of the progress and in response
to updates, change some UI
on the screen, a
UI progress view,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on the screen, a
UI progress view,
and some detail label text.
So right here after
we create the request,
we are going to call
addObserver.
This class will be the observer.
The progress property
of the request is the
object we are observing.
The key path of the progress
is fractionCompleted.
And as always when
using KVO it's important
to specify a unique
context pointer
which I have defined
elsewhere in this file.
Also, we are going to go ahead
and unhide a UI progressView
that I have already hooked
up in our storyboard.
When we get our response, that's
a great time to removeObserver
because after that point
we are no longer interested
in updates to the progress.
And then on the main thread we
are also going to unhide the --
sorry, rehide the
UI progressView
because we don't want
our user to be staring
at complete progress bars.
So that adds ourselves
as an observer
and then the second part of
this is to actually do something
when the value changes,
and we do that in the
traditional KVO method,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we do that in the
traditional KVO method,
observeValueForKeyPath.
So here we are first
checking to make sure
that this is the update that
we are interested in observing
by checking the context
pointer and the key path,
and on the main thread, again,
because this update will come
on a non-main thread,
we are going
to update our UI progressView by
setting its progress property,
and we are going
to take advantage
of NSProgress's support for an
automatic localized description
by using its
localizedDescription method.
Let's go ahead and run this
again and see how that works.
So this time when I visit
Italy, you will notice
that at the bottom our detail
label text has been updated
to show a percent completed, and
then as the download completes,
our UI progress view is
updated to show the user
that something has happened.
Also, again, we didn't
delete the content eagerly
so if we go back and
visit Italy again,
you notice that the content
is immediately available
because it's been cached by
the On Demand Resources system.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because it's been cached by
the On Demand Resources system.
Let's go back to our slides.
So a few more things about
the progress reporting part
of that demo.
So, again, you can find
information on the progress
of the request by looking
at the progress property.
We also support cancellation,
pausing, and resuming
of the request, and those
are methods you will find
on the NSProgress property,
not the request itself.
Now, actually in iOS 9 and
OS X 10.11 we have made a lot
of really great improvements
to the NSProgress
class in Foundation.
So to learn more about how to
use it, how to incorporate it
from this class and from
elsewhere into your application
in a great way, I really
recommend you check
out this talk on
Friday, Best Practices
for Progress Reporting.
So the beginAccessing method
that we used goes ahead
and downloads content if
it's not already available.
However, sometimes you may
want to only use the content
if it's already available
on disk
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
if it's already available
on disk
without triggering a
download and you can do
that using what we call
a conditional request.
Here's the method:
conditionallyBeginAccessing
ResourcesWithCompletionHandler
(without space), and you can see
that the closure here
has a Boolean argument,
so the answer is
either yes or no,
depending on whether the content
is already available or not.
So in the view of our state
machine, we start off in init,
we move to this conditionally
requested state
by invoking this method.
You will get your response,
either it's available or not.
If it's available, then
the resources are available
to you just as if you had
called the other method.
So, again, be sure to
call endAccessingResources
or allow the object
to be deallocated.
If it wasn't available, at
that point you have a choice:
You can either do nothing
or you can call the
beginAccessingResources method
to cause that download
to happen.
And finally, there are two kinds
of priority APIs on this class
that I want to talk about.
The first is called
a loading priority.
This is a double, and
it provides ordering
for the outstanding
requests in your application.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for the outstanding
requests in your application.
The value ranges from 0 to 1.
So a value of 1 is
the highest priority
and the value of
0 is the lowest.
What we do with these values
is provide a hint to the system
about which outstanding request
should be prioritized first.
So because the value is not
compared across applications
on the system, but
only within your app,
you can use these
values as you choose
to help order the requests
according to your needs.
We do have a constant that
you can use here actually;
it's called loading priority
urgent, and this is for the case
where the user is doing nothing
but waiting for the download
to finish and we will
talk a little bit more
about this later.
The second kind of priority is
called a preservation priority.
So this method provides ordering
of purging for unused tags
in your app, so this
is not associated
with a particular request,
which is why it's an extension
on NSBundle, but instead
with a tag or set of tags.
Again, the value
ranges from 0 to 1,
with 0 being the least
interesting to keep
and 1 being the most
important to keep.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and 1 being the most
important to keep.
So you can use this to
tell the system a hint
about which content is the most
important to keep if we run
into disk space pressure.
And the value, again,
is compared only
across your own application,
not between applications
on the system.
So with that, I would like
to bring back Steve to go
over some more best
practices about using ODR.
[Applause]
>> STEVE LEWALLEN:
Thank you, Tony.
All right, so now we
have an overview of ODR,
we understand what it's about,
and we have seen it in use.
So now let's talk about
how we build the optimal
ODR application.
Now, to do that, the
first thing that you need
to do before you start
tagging assets is
to consider your app's behavior,
because this will inform
how you need to tag assets.
There are three patterns that
I can offer up for you today
as to how you might
use ODR content.
The first pattern, this is
like Tony's iTravel app.
We couldn't quite anticipate
where the user would want to go.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We couldn't quite anticipate
where the user would want to go.
This is random access.
And in a random access app,
especially when the assets are
larger, the content in total,
what you want to do is tag
more assets and read things
in progressively so that as soon
as the user makes a decision,
they can start to
see some progress
of that decision in the UI.
The next pattern is
called limited prediction.
This is like an open
world game in some sense,
where though there may be
almost an infinite number
of possibilities,
at any one point
in time there's only
a limited subset.
An so again what you want
to do is have many tags
tagging smaller size content,
progressively read that
in, but also be prepared
to discard content that the
user has chosen not to view, as,
for example, the player in the
game moves around the world
and they have left some
location, you should stop using
that indexing resources,
getting rid
of the NSBundleResourceRequest
object.
Now, the third pattern is the
pattern than I've been using
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, the third pattern is the
pattern than I've been using
in my example; it's
the levels in the game.
This is the linear
progression game much
like a first person shooter.
In this case, the biggest task
that you have as a developer is
to anticipate well ahead of
time what will be needed next,
but you are pretty
much assured that all
of the content will be
consumed, and so you just want
to start loading that content
within a reasonable
amount of time.
Okay. Speaking of time, there is
a rhythm to how you use ODR API.
So let's give ourselves
a timeline
from the point the app
launches until it exits.
Now, the well-written ODR
application will anticipate
needing assets well beforehand.
Remember, this is a
network-based app.
It's going to have to
download it from the Store
or another location
we went over earlier,
but that will take some time.
So as soon as you anticipate
needing them, assets,
that's when you call
beginAccessingResources
and that will kick
off the download
if the assets are not
yet on the device.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
if the assets are not
yet on the device.
Now, the best-laid plans can
hit, well, network problems,
for example, and other
things that may result
in you needing the resources
before they are actually ready.
And, again, in this case, you
want to bring up the progress UI
that Tony mentioned using
the progress property
and NSBundleResourceRequest.
Share your learning screen,
allow the download to continue,
and then at some point
that will actually finish,
the resources will be
available and you can take
down the loading screen, and you
can begin to use the resources.
Once you are done using the
resources, you absolutely want
to call endAccessingResources
or allow the
NSBundleResourceRequest object
to dealloc in order to tell the
system, hey, I'm done with this.
And remember that that
doesn't mean we go off
and delete that content.
We are just making a note of it.
So this is the basic timeline
you need to be aware of,
and you can make multiple
these requests simultaneously,
you just need to remember
the basic parts of this.
Okay. Now, I had talked about
how ODR can benefit the user
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Okay. Now, I had talked about
how ODR can benefit the user
by improving the
installation experience.
So how is that done?
Well, we need your
help to do that.
What you need to do
as a developer is
to consider the assets that the
user is going to need as soon
as they launch the app.
When you know what those
are, say the first level
in the game is a good one,
then you want to take the tags
for those assets and put them in
Xcode's Initial Install Tags UI.
This will tell the ODR system
to make sure all the assets
with those tags are
downloaded before the app shows
as being 100% installed.
That way when it does show
it's completely installed
and the user taps on
it, it's ready to go.
They are ready to
play that game.
The size of your app that you
see in the App Store is going
to be the size of the
dot app plus the size
of the initial ODR content
that you have tagged here,
so just to be aware
and we will talk
about this point a
little bit later as well.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Okay. So now let's talk about
automating the installation
of ODR content between the
time the app is actually fully
installed, but the user has
still not actually launched it,
because you can automate
that period of time as well.
You could use this, for example,
to make sure the second level
in the game is loading,
or, for example, tutorials.
Maybe some customers want
to see these tutorials
and others don't, so you are
not going to prevent the app
from actually running if
the tutorial isn't there,
but you would like to
make sure it is there
if you have the opportunity.
And to do this, you can also
use Xcode's resource tags UI
and put the tags in the
pre-fetch tag order section.
In the actual order
that you place them
in this section is the
order we will download them.
All right.
Now, let's talk about caching.
We mentioned caching earlier
and we showed how the
system purged game Level 1
to make space for a new level.
The first thing you need to
know is we only purge content
when the system is low on space.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
when the system is low on space.
And the only content
that is there
to be purged is ODR content.
When we do come to that point,
there are several attributes
that come into play to inform us
as to what decision we
should make in deleting
which asset pack, for example.
These are pretty obvious.
One is when did you
last use the asset.
So, for example, game Level 1
that was played last Tuesday,
that's probably a better
candidate to delete
than the level you
finished last night.
Also as Tony mentioned you
have preservation priority
which is a ranking
you can place on tags.
So this is your own
ranking but we take
that into account as well.
And finally, we take
into account the
running state of the app.
We will not delete any asset
packs from a running app
when those assets
are actually in use.
Now, there are a few
strategies you can use
to help preserve
your ODR content.
One of them is to
avoid overpurging.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
One of them is to
avoid overpurging.
What do I mean by this?
Let me give you an example.
Let's say the system
needs 100 megabytes,
and it's gone everywhere else
to find the space,
it can't find it.
Through the attributes we
discussed just a moment ago,
it's located one of
your asset packs,
but that asset pack
is 512 megabytes.
We are going to have to
purge that asset pack
if it met the conditions
so we will have overpurged
by 412 megabytes, so it's better
to keep your asset pack smaller.
As I mentioned earlier, this
is also good because you want
to progressively download
and consume content,
so smaller is better
there as well.
But smaller is also better
here to avoid overpurging.
All right.
Another thing you might
be tempted to do is
to tag everything with a
1.0 preservation priority,
but that doesn't
really help you.
That just makes more of
your assets look equivalent
to the system when it
comes to it looking
at purging something
from your app.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
at purging something
from your app.
So you want to use the
1.0 value judiciously.
And finally, again, make
sure, because we look
at the last used date that you
endAccessingResources as soon
as you are done with the tags
or you allow the
NSBundleResourceRequest object
to deallocate as
soon as possible.
This helps the system know
how you are using the assets
so it can make the
best decisions.
Okay. Now, I would be
remiss if I didn't talk
about performance in this talk.
One of the things that
we have been doing here
in this session is to encourage
you to download content
in the background ahead of time.
But we don't want to
do that at the expense
of consuming more
resources of the system
than your app can tolerate.
So we balance the speed of a
download with the resources,
CPU and otherwise, that we
consume while we download
and process these asset packs.
That's the default.
But if you get into a
situation or you just want
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But if you get into a
situation or you just want
to manage this all on
your own and you want
to put full throttle
down, then you can set it
with the urgent priority, and we
will disregard that balancing,
and we will download the
content as quickly as possible.
Again, as Tony mentioned, this
is a good time to set this
when you are putting
up the loading screen,
but your app actually may be
tolerant to the CPU usage,
for example, that we have on the
system when we are doing this.
So it's all up to you.
And also, we should talk
about performance testing.
You need to do real world
testing for an ODR application.
An ODR application is a
network-based application.
So when you have your
device connected via USB
to your MacBook Pro, for
example, running Xcode,
that's not a real
world scenario.
That's just far too fast.
So what you want to do is
to test your app using perhaps
TestFlight or Xcode Server.
And then also use the Developer
Tools Network Link Conditioner
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then also use the Developer
Tools Network Link Conditioner
to test out various
networking conditions
that might cause
issues for your app
in how soon ODR content
is downloading.
In fact, I would recommend using
this tool for any networking
that you are doing
from your app.
It's really great.
If you haven't used it
before, I would like to cover
that now just briefly.
So when you attach
your device via USB
to your MacBook Pro again,
for example, of running Xcode
and you then you go to Settings
on your device you are going
to see this entry,
a developer entry.
You tap that and you
will see numerous numbers
of developer settings that you
can use in logging, et cetera,
and in the middle there is
the Network Link Conditioner.
So you tap that, and now you
see the options that you can use
to cause various
conditions to be present
when networking out of your app.
To use this, you enable the
Network Link Conditioner,
and then you decide what type
of situation you want to create.
Perhaps you want to mimic 100%
loss or very slow network,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Perhaps you want to mimic 100%
loss or very slow network,
or perhaps a high latency in
DNS lookups, or you just want
to reproduce a really
bad network.
So this is a great tool
to use while you test
your ODR application.
Okay. Now, speaking
of networking issues,
you may encounter a couple of
networking errors along the way.
One of them is this one.
Obviously there is no network.
So if your app needed ODR
content and there is no network,
you need to be ready to
handle this situation.
Another area you may encounter
is resource unavailable,
and basically what this means is
we thought the ODR content was
going to be in a certain
location and it wasn't.
Generally the case or the cause
of this will be, for example,
setting up a server
inside of your own company
and someone is managing
it, moving things around,
and they just weren't ready
for the users to use that app.
So just to be ready
for that error as well.
Another class of error that
you can encounter relates
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Another class of error that
you can encounter relates
to storage space.
So ODR will allow your app
to have a maximum of 2 gig
in use at any one time.
What this means is
your app is running,
you have called
beginAccessingResources on tags
that amount to 2 gigabytes,
you haven't called
endAccessingResources on any
of those, you haven't allowed
the NSBundleResourceRequest
to dealloc for any of those,
so that 2 gig is in use.
And then you go and
make another request.
That goes over and you
are going to get an error
in your request, a callback.
So be aware of that as well.
Also, of course, we just might
hit a low-space condition
on local storage on the device.
And you will get
this notification.
This can happen if you
have initiated a download
or maybe it happens long
after you made that request.
So just be prepared
to handle that.
To handle it you could
do a couple of things;
you could endAccessingResources
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you could endAccessingResources
on any ODR content
you no longer need.
And you can also look
at what you are storing
on the local device, perhaps
some caches or other documents
that you actually don't need,
and you could free those
up and delete those.
So now let's talk
about cellular data.
Again, an ODR application
is a network application.
So it is governed by the
same cellular data switch
that controls the rest
of your app's networking.
So if that switch is
off, you will not be able
to get any ODR content.
If that switch is on, and you
are downloading ODR content,
then any of that data that
you use over cellular is going
to be attributed to your app.
So this should encourage
you to make sure you ask
for what you need
and not any more.
We don't want to run
up a user's bill.
And finally, the 100-megabyte
cellular download limit still
applies to ODR applications,
so remember where I had
the dot app plus the size
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
so remember where I had
the dot app plus the size
of initial ODR adding
to up the size
of the app listed
in the App Store?
Well, if that's greater than 100
megabytes, your app is not going
to download over cellular,
just like a normal app
that was greater than
100 megabytes would not.
So you need to be aware of
that limitation as well.
Finally, as we begin
to wrap this up,
there is some vital statistics
that you need to be aware of.
First of all, although you can
have an ODR application have a
size of up to 20
gig in the Store,
a maximum of 2 gig is
reserved for your dot app.
So your dot app itself?
Remember when we teased apart
the assets from your dot app,
we had the asset packs
and then your dot app?
That dot app can be a
maximum of 2 gigabytes.
The rest of it can be
ODR content up to 20 gig.
And you can have a
maximum of 2 gig of initial
and pre-fetched ODR content.
This is the content we
set up in Xcode's UI
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is the content we
set up in Xcode's UI
to download things
during the app install
and right afterwards.
You can have a maximum of two
gigabytes of that combined,
and as I mentioned just a moment
ago, a maximum of two gigabytes
in use at any one time.
And finally, a given asset
pack can be a maximum
of 512 megabytes.
So if you take a single tag
and tag a bunch of resources,
and that adds up to more than
512 megabytes, Xcode is going
to give you a warning, but
it will allow you to continue
to develop your app or game, but
when you submit to the Store,
you are going to get
a submission failure,
and the submission failure
error message will explain why.
Okay. So in summary, On Demand
Resources is a dynamically
loaded content system.
Hosted in the App Store.
This can be automated
to download content during app
install time and by request.
You can prioritize or
order these downloads.
We have an intelligent caching
mechanism so that, for example,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We have an intelligent caching
mechanism so that, for example,
we could get rid of that game
Level 1 in order to make space
for a following level, and
you get 20 gig in the Store.
So for more information we
have a great new documentation
on ODR; we also have
sample code,
and the developer forums are
always a great place to go.
And if you still have
questions you can follow
up with our App Frameworks
evangelist.
You may have missed
a couple of sessions
on other technologies
that relate to ODR.
Those will be available via
videos on the developer website.
And there is going to be a
session as Tony indicated
about progress on Friday as well
as a lab tomorrow from 11:00
to 1:30 dedicated to ODR.
Tony and I and the extended ODR
engineering team will be there
to answer any of your questions,
get you started using
ODR content,
and listen to any
suggestions you might have.
Thank you very much!
[Applause]