-
On-Demand Resources and Data Storage
An important part of developing for tvOS, supporting On-Demand Resources allows your apps to contain up to 20GB of content. Learn how to create smaller app bundles, enable faster downloads, and add richer app content. Understand space-friendly packaging, dynamic downloading and accessing extended app content, content hosting strategies, download prioritization, and more.
Apple TV Tech Talks - Session 5 - tvOS
-
My name is Curt Rothert and I'm a UI Frameworks Evangelist and I'm going to talk about On-Demand Resources and Data Storage.
So as we mentioned earlier, on demand resources is a fundamental part of building your application for tvOS.
And just through this talked I'm going to refer to it as ODR just for the sake of your ears and my tongue. So I'd like to give you a quick overview of what ODR is, we'll talk about the concepts, the fundamentals, the features and behavior on tvOS, and then we'll dive into the API. Finally we'll switch gears and we'll talk about Cloud Storage with respect to your applications persistent data. So let's get started. So to understand ODR it's instructive to think of the structure and the workflow for creating a traditional application. So every application is composed of a set resources, including your executable-and that's your compiled code-as well as a set of resources required to get your application up, running and interactive.
Now consider the case of a level based game. There are additional resources for each of these levels and this could be sound files, textures, map data; really, anything that's unique to each level. So when you're ready to publish this application, you submit it to the App Store and then as somebody is navigating the App Store on their Apple TV they find your application, purchase it, then they get an exact copy of that application downloaded to their Apple TV. And that application isn't alone. In fact, it's sharing space on disk with all of the other applications that that person has purchased. And as they continue to buy more apps, download more content, they'll eventually run into disk space pressure and run out of space. So how do we deal with that? Well, a few observations, one is that not every application is created equal and I mean that not all applications are really frequently used.
The other observation is that even for applications that are frequently used it's not the case that all parts of that application are used at any given time.
For example I can be in level 10 of a video game and I wouldn't necessarily need the resources for level five, nor do I need the resources that are required for level 30 just yet. And also consider the initial download experience. If I don't need the resources for level 30 until many hours into the game play then it's not necessary to have that downloaded during the initial install, which can greatly reduced the time required for purchasing that app or game getting straight into the game play. So it's with all of this in mind that we start to think about ODR.
So how do you package up an On-Demand Resources Application? Well, you start with the same applications, the same content, the same resources, we haven't taken anything away and we haven't reduced it at all, but conceptually we're breaking apart the non critical resources from the main application, packaging those into on demand resources that then we can manage and download separately. So like before, you publish it by submitting it to the App Store, but now when somebody buys it, rather than getting the entire application, they're only getting a subset of it.
The main application, those initial base resources and even perhaps the first level, so they can quickly get in to launch the application, get into and it in this example, we marked that we're now using the resources for level one as they begin playing that level and your app can make some decisions here.
It can anticipate that it's going to need the resources for a level two. So using that same ODR API, it indicates to the system and that system will then download those resources asynchronously in the background. So as we move from level one to level two, you indicate that you're now using the resources for level two. Now, your application can do two things. Like before, it can anticipate that it's going to need the resources for the next level but also through the same ODR API, it indicates that is no longer using the resources for level one.
So nothing happens here and none of your contents removed, but let's consider that this is the case where you've run out of disk space and we can't download the content for the next level. So the system will try to clean up as much space as it can by purging any temporary files, cache directories, but as a last resort it will fall back to looking at ODR content. So in this case they could choose then to purge the level one content, which then frees up enough space that it can download the next level. And now, you're able to move on to the next level.
Indicate that you're no longer using the resources for level two and so on. So this is the basic idea behind ODR and in the case of a game it's really fast to go from buying the app, straight into the game play and it's always occupying a smaller, more manageable amount of space on disk.
So On-Demand Resources provides you as the developer, a system level service for managing this dynamically loaded the content.
All of your ODR content is hosted by Apple on the App Store right along with your application. You have the choice of downloading some of that ODR content during the initial install or at run time dynamically when you need it. We have a priority system so that if multiple downloads are happening simultaneously, you can prioritize one above another. Finally, we are intelligently managing which content is cached on the device, which needs to be pulled down from the App Store, and what can be purged. Now, for your customers this is a much better install experience. The initial download only needs to include what the app needs to get up and running and that can happen very quickly.
Secondly, since we are managing the application's footprint for your app as well as every other app on the Apple TV, that means that we have much more space available.
So you can have many more applications ready to go rather than if we have to download all the content for all those applications.
And third, because a lot of this content is actually hosted remotely, each application can be much larger than traditional. Traditional applications have max out at four gigabytes but using the ODR you can use up to 20 gigs; so let's dive into some of these details. So the way you would approach ODR in an example like this is to begin by separating your assets for the levels out into what we call asset packs. And then the rest of your content, which is your base resources and your main executable stuff, that stays in the main .app. You'll group this content together in Xcode by giving each asset a tag, and tags are really just simple strings that you type in like level one, level two, tutorial.
And they can be applied to individual files or to entire folders of contents, so all of your assets can be grouped together as one asset pack that's downloaded when you need it, and each asset can have multiple tags as well so if you had a sound file or some textures that were required in a tutorial as well as level five, you'd give it both of those asset tags and the system would manage that appropriately.
So the things that you can tag are effectively any read-only resource that you would have packaged into your application anyway, like images, textures, sound files, data, you can even tie your in-app purchases to be backed by ODR content. The one exception is you can't include executable content in asset pack. All that has to be included in the main .app. There's a couple of special tag categories that I'll talk about now. First is the Initial Install Tags. Now you use this for resources that you want to download along side with the application when it's downloaded. So in the example that we showed earlier, level one was included at the same time and that was one of this Initial Install Tags. This is for content that you know that you're going to want immediately on first launch and this is really important to use. You don't want somebody to be able to purchase your application, launch it immediately and then be stuck at a loading screen as it's loading the content for that first level.
So this can be up to two gigabytes in size and this is included in the total application size as reported in the App Store.
The second special category is called the Prefetch Tags, and these will start downloading immediately after the application is installed.
There's a fine line here. So while the Initial Install content is for stuff that you want there on the initial launch, Prefetch is for additional content that you know you'll want soon but you wouldn't want to block the initial launch of that application because of it.
And the maximum size here for both of these is four gigabytes, so while the Initial Install Tags can be up to two gigs, the Prefetch tags can contain up to four gigs minus that Initial Install size. And this is where you specify it in the UI, so in Xcode, in the project build settings if you navigate over to the resource tags, you can see here there's this group for the Initial Install Tags. We've included our level one content, our enemies and our map as well as our tutorial. And like I said before this is downloaded along with the Initial Install of the application.
Second section is this Prefetched Tags. Again, this is for content that is downloaded immediately after the application is installed.
And is downloaded in the order that you specified here so in this example, this is level two data, our maps and enemies.
And finally all of the rest of our standard ODR content is highlighted by the download only On-Demand name, and this includes for this example our Holiday Theme, our level three data, as well as a special weapon that we might have tied to an in-app purchase.
So here's some numbers to remember. Your base application, and that's your base resources and executable, can be up to 200 megabytes.
Your On-Demand resource content can be up to 20 gigs. The amount of ODR content that you can have in use at run time at any given time is up to two gigs.
The Initial Install Tags can contain up to two gigs of data. And the Initial Install and Prefetch tags together can be up to four gigs.
Each asset pack can contain up to 512 megabytes but we recommend something closer to 64 as that seems to be a nice sweet spot in terms of download size when those assets need to be downloaded, as well as granularity when the assets need to be purged. And the total number of asset tags that you can have in your application is up to 1000. OK, so that's the high level view of how ODR works, so let's crawl through some of the details of the API and in so doing we'll explain some of those features along the way. So to access your ODR content, there's really one new class that you access that through and that's through this NSBundleResourceRequest. This manages all of your access to the ODR content. You'll create one of these for each set of tags that you're interested in accessing the packs associate with. And it's also how you set the download priority, track the download progress, and handle any errors that may come up. You create as many of these objects as you need and the system will reference count the tags that are in use. The most important design point here is that the request is decoupled from the use of those resources, so all of the existing file access APIs that you've been using like - [NSData dataForContentsOfURL:], those just remain as is, unchanged, but what you're doing instead is bookending their access with one of these resource requests. And that's important because you're indicating to the system that those resources need to be available at the time that we access them. So here's what the API looks like.
First there's an initializer that takes a set of tags that you're interested in requesting. And then to begin the request you'll use this method, beginAccessingResources WithCompletionHandler. This can do one of two things. If the asset pack associated with those tags has been downloaded already, then it will immediately call your completion handler and you can continue you accessing all of that content like you normally would. If the asset packs for those tags haven't been downloaded, however, this will trigger an asynchronous download, this method will return immediately and then when those asset packs have completed downloading, or if there's an error, your completion handler will be called with that. When you're done using your resources you need to tell the system that you're finished by calling this endAccessingResources. You can track the progress of an in-flight download using this progress property, and this is a bit of a subtle point though. Your application should anticipate far enough in advance what content its going to need so that it's available at the time that you need to access it. But if you do encounter a case where that download is still in progress, you should probably present some UI to the user, so they know and can anticipate how long that download is actually going to take. NSProgress supports key value observing and you can integrate it with a UIProgressView if your UI is using UIKit. Or you can use the fractionCompleted property to present that progress in your own in-app UI. It's interesting to note here that you can also control the progress through this progress property, using either pause, resume or cancel. So you might want to use this in a case where they're waiting at the download screen but they change their mind and they want back out to the menu, so you can choose to pause or cancel at that time.
Next up is the loading priority, so this sets a relative priority between any outstanding request that are for your app, the value here ranges from zero, being normal priority to one being the highest priority. And there's also this special urgent priority. This is for the case where the user is actually blocked.
So they're sitting there, waiting for their content to download. The important thing here is by setting this priority, they will suspend any existing downloads that are in flight. And normally ODR tries to minimize its usage of the CPU. And that's so it doesn't interfere with your application's performance or frame rate but when you set this priority, you're essentially telling the system, this is critical to get down even at the expense of your own application's performance. Now, as I mentioned before, to trigger a download of content, you would call 'begin accessing' and that will get the download going if that content isn't there but what about the case where you just want to know if the asset pack has been downloaded. Now this can be useful in a case where you have a level chooser UI, for example.
So, you want to indicate to the user, what levels you can play immediately versus what levels have to-- which you're going to download.
So you can do that using what we call a conditional request. So this method, conditionallyBeginAccessing ResourcesWithCompletionHandler, that will return true if the asset pack has already been downloaded to disk, or false if it doesn't exist.
And it won't trigger a download if it doesn't exist. But the important point here is it that if it is on disk, it's also granting you access to those resources.
So, you'll need to make sure that you end the request when you're done. So the last part of the API to talk about is the preservation priority. This let's you set an order for the purging of the tags in your app when they're no longer in use.
So you can use this to give the system a hint about which content is the most important to keep around if we do run out of disk space.
So it's an extension on NSBundle and the method is called setPreservationPriority and it takes a value from zero being the lowest priority to one being the highest priority. Now, this value is compared only against the other assets in your application, it's not compared against asset packs in other applications. So by setting all of your content to 1.0, it doesn't somehow make you special.
Now in terms of caching, I mentioned that the system is managing what content is kept on disk, what's pulled down from the App Store and when stuff is purged. But the important point here, is that we'll only purge content when we do run out of disk space.
So if you have plenty of disk space, we'll keep all those asset packs cached on disk for immediate access.
But when we do come to the point where we're going to purge, there's a couple of attributes that feed into this and this is just pretty obvious.
So, first is the last used timestamp. So as an example, an asset pack that was used in a game that I played last month is probably more likely to get purged than an asset pack that I used in a game this morning. The second is the preservation priority which we just talked about and this is the way you hint to the system which assets you think should be kept around. And the third we take into account is the application's running state. We won't delete any asset packs that are in use by a running application.
And it's important to note here that only ODR content was purged. It's not the case that your resources or base executable is going to be purged. So before we move on and talk about data storage, let's just talk about how you test your ODR implementation.
When you've implement ODR in your application and you're finally ready to host it on the App Store, the App Store is responsible for hosting all of your ODR content at that time too. But what about while you're developing your application? Well, where is it getting hosted at that point? You have four options. You can host it via Xcode or Xcode Server. You could use TestFlight.
You can embed the ODR content directly in your application. Or you could host it on your web server. So we'll talk about each of these now.
During development, this is the most common scenario, where the Xcode can act as a stand-in for the App Store and then host your ODR content when you access it.
There's a couple of things to note here and these are pretty minor. The Initial Install and the Prefetch tags are not supported.
And what I mean by that is that they will only come down immediately when you first access them. And secondly the download time might be artificially fast and that's because this content is being hosted locally rather than remotely. But this is really great for testing the correctness of your ODR implementation. And we have this really cool Disk Gauge so you can monitor your usage.
So when you're running your application via Xcode, you can click on the Disk Gauge and scroll down to the on-demand resources section.
And this will show you all the tags you have in your application as well as their size and their status. So this is really instructive as you're running your application, you can see the status change and verify their correctness of your implementation. This is a really good debugging tool.
Now, when you're happy with the correctness of your ODR implementation but you want to get some real world performance testing, you can use TestFlight. So TestFlight is fully integrated with ODR, and the behavior should be the same as what you see in the App Store. All of ODR is supported in TestFlight, in tvOS 9.1 including the Initial Install and the Prefetch tags. And this let's you try your ODR under a variety of network conditions either just through normal real world use or by using the Network Link Conditioner on your Apple TV and I'll show you how to do that.
So on your Apple TV, if you go to the settings. Navigate to the developer menu then go to the network link conditioner section, you can enable it here and also choose a profile. So, each of these profiles they provide a different cap on speed, latency and reliability.
So you should use this when you're testing your ODR especially to check for any possible error conditions that you should handle. Third option for hosting your ODR content is embedding it directly into your build.
And this isn't really for testing ODR per se. But sometimes, you run into a situation where you need to share your build with a colleague and you don't want to go through TestFlight or through Xcode. So this is how you will package all of your ODR directly into the applications for ad hoc distribution.
So there's a build setting in your project build settings, or you can do this but just keep in mind, this will inflate your application's size from what you would see in the App Store. And also this isn't actually usable for the App Store. Setting this won't allow you to submit to the App Store.
And then finally you can actually host all of your ODR content on a web server. Enterprise apps can use this for in-house distribution of apps that use ODR. And regular apps can use this prior to submission, just for normal ODR testing.
This is a deep topic all on its own and I just want you to be aware that it exists, but I'm not really going to go into details here.
If you want more information, check out the On-Demand Resources guide. There's a section called Hosting On-Demand Resources.
So that wraps up ODR. The last thing I want to talk about is explain how data storage works on tvOS.
To do that, let's just kind of rewind and think about the mindset of the Apple TV, it's this always connected device.
And as we've been talking about ODR where all of your content could be hosted up remotely, you can think about your application's data in the same way.
So to that end, you have pretty limited persistent storage options. And you should probably think about pushing your application state up to the network and only pulling it down when needed. I think along with this platform is this expectation that it synchronizes with all of your other devices. So, for example if you had a universal purchase, you would expect that your progress or application state or your game progress would be synchronized with your other devices as well. So as I mentioned your local options for storage are pretty limited. You can use NSUserDefault up to a 500k limit or you can store data into the Keychain, but this is really intended for passwords and other access tokens. And keep in mind that data that's stored here actually doesn't get backed up. So it's not going to survive the device being erased or restored. So for any data that can't be lost, our guidance is to use the network and your options here are to use the iCloud Key-Value Store, CloudKit or host the data on your own server.
So I'm going to talk about the first two options in case you're not familiar with them. The iCloud Key-Value store provides just a simple key-based storage. It's like NSUserDefaults but it's online and persistent. You can store up to one mega byte per user in up to 1024 keys. And CloudKit is considerably more sophisticated. It provides a public container for each app and that's visible to all instances of your application. It has size and data transfer allocations that scale with the number of instances.
And then it also has a private data store for each particular user. So that data itself can be structured like arrays or dictionary or it can be both data like images or other application state. And we introduced a JavaScript API to CloudKit so that that could be accessed by not IOS services as well. So we did a session on that, at WWDC this year, so check that out.
A couple of caveats here, if you're using iCloud or CloudKit is that these require iCloud accounts. So, if there's somebody using your application and they're not signed into iCloud you'll have to-- your app will be able to access the public container for that information but not the private or the per-user user data and you won't be able to access the iCloud Key-Value store either. So if your app requires either of these, you need to store your data when the user is actually logged in. So if they're not logged in, indicates that the user in your UI, they should go to the setting's app and log in from there. And secondly, several people might be sharing this device but only one iCloud account can used be at a time.
So just keep that in mind. Now, in terms of other types of temporary storage, things that are larger than 500k that you want to store locally.
These could be for things like you pull data down from a web server but you want to cache it locally for immediate access or you've generated some data that was actually expensive to create and you want to keep that around. You can use the caches or the temporary directory as well.
But if you do, just be aware that those locations are subject to being purged and specifically when we run out of disk space and ODR needs to make more, these are the first areas that it's going to purge content from. So you're free to use these, just use them wisely and be prepared for anything there to be gone the next time you launch the application, it might need to be regenerated. So since that went pretty quickly, let me just recap your options here. Your best options are the iCloud Key-Value Store or CloudKit. Those are online, they're persistent, and they can be shared with your other iOS devices. Your local options are to use NSUserDefaults or the Keychain but as we mentioned it's mostly intended for settings and other small pieces of data and they're not backed up. Finally, for local storage you can use caches and temps for bigger blocks of data but those could be purged.
So just keep that in mind. So for more information about ODR, you can check out the On-Demand Resources guide and the class reference for NSBundleResourceRequest. For persistent application data you can check out the CloudKit Quick Start guide and also the DemoBots sample code has been written with ODR so that you can use that as an example as you build your ODR application.
-
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.