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 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.
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 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.
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 in the Store, but with ODR your app can now be up to 20 gigabytes.
Most of this, of course, is ODR content.
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 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 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.
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.
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.
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.
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 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.
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 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 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 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 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.
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, 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.
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 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, 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.
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 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.
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, 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, 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.
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 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.
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.
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.
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.
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 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.
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 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.
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.
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.
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.
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.
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.
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 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 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, 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 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 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 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 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, 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!