An Introduction to On-Demand Resources on iOS and tvOS

An Introduction to On-Demand Resources on iOS and tvOS

Introduction

Alongside iOS 9 and watchOS 2, Apple introduced on-demand resources, a new API for delivering content to your applications while also reducing the amount of space the application takes up on the user's device. With on-demand resources, you can tag specific assets of your application, have them hosted on Apple's servers, allowing your users to download them when needed. In this tutorial, I am going show you the basics of on-demand resources by creating a basic image viewer application.

Prerequisites

This tutorial requires that you are running Xcode 7+ and are familiar with iOS development. You will also need to download the starter project GitHub.

1. On-Demand Resources

Benefits

On-demand resources were introduced in iOS 9 and watchOS 2 for the main purpose of reducing the amount of space individual apps take up on a device. Another important advantage of on-demand resources is that your app can be downloaded and opened by users much quicker.

On-demand resources work by assigning unique tags to resources within Xcode to create what's called an asset pack. These packs can include anything from asset catalogs (images, SpriteKit textures, data, etc.) or even just other files, such as OpenGL and Metal shaders as well as SpriteKit and SceneKit scenes and particle systems.

When you submit your app to the App Store, these resources are also uploaded and are hosted there in order to be downloaded at any time. To download asset packs at runtime in an app, you simply use the tag for each pack that you assigned in Xcode.

Categories

The two main aspects of an app that uses on-demand resources are the app bundle, which is full of executable code for your app and essential assets, such as user interface icons, and asset packs.

For these asset packs, there are three main categories which you can organize in Xcode:

Initial Install: This is for content that is needed for your app to run for the first time, but that can be deleted later. This could include the first few levels of a game, which are no longer needed once the player progresses far enough into the game.

Prefetched: This category includes content that you want to be downloaded immediately after your app has finished installing. This type of content is recommended for resources that are not required for your app to function after installing it, but that are needed for a better user experience. A good example are tutorials for a game.

On Demand: This category is aimed at content that you need at a later time and your app can function without. When working with on-demand resources, this is the most common type of category that you will use.

Limits

Apps that are built with support for on-demand resources must also stick to the following limits with regards to file size:

2GB for the iOS app bundle

2GB for the initial install tags

2GB for the prefetched tags

2GB for in-use resources. This is only important when your application is running and using on-demand resources.

512MB for each individual asset pack. No single tag can contain more than this amount of data. If you go over this limit, Xcode will give you a warning and will allow you to still test and develop your app. Any submission attempts to the App Store, however, will fail.

20GB for all the resources hosted by Apple. This is the total amount of resources your app can possibly download at any one time. While only 2GB can be used at any one time, if a user's device has enough storage, up to 20GB of your resources can be downloaded and made accessible to your app at any time.

App Slicing

Note that the 20GB total does not account for app slicing while all of the other totals do. What is app slicing? App slicing is another feature that was introduced in iOS 9 to reduce the size of applications. It does this by only looking at the resources specific to the device that the app is being installed on. For example, if asset catalogs are used correctly, an app that's installed on an iPhone 6 Plus or 6s Plus, only needs to download the 3x scale images and not worry about the 1x and 2x scales. For on-demand resources, the 20GB of total resources you can upload to the App Store servers is the total amount across all device types. All of the other limits are for each specific device your app is being installed on.

Deleting On-Demand Resources

In terms of data deletion (purging), asset packs that your app has downloaded will only be removed when the device your app is installed on is running out of available space. When this happens, the on-demand resources system will look at all apps on the device and upon selecting one will look at the preservation property of each asset pack as well as when it was last used. One important thing to note is that asset packs for your app will never be purged while your app is running.

2. Assigning and Organizing Tags

Open the starter project in Xcode and run the app in the iOS Simulator. At the moment, this basic app contains a collection of images each with a combination of one of three colors (red, green, or blue) and one of four shapes (circle, square, star, or hexagon). With the app running, navigate to Colors > Red and you will see a single red circle image displayed on the screen.

In this app, we are going to set up a total of seven asset packs, one for each color and one for each shape. Another great feature of on-demand resources is that a single resource can be assigned more than one tag. The red circle, for example, can be a part of both the Red asset pack and Circle asset pack.

The on-demand resources API is also smart enough to not download or copy the same resource twice. In other words, if an application had already downloaded the Red asset pack and then wanted to load the Circle asset pack, the red circle image would not be downloaded again.

In Xcode, open Assets.xcassets. You should see all twelve images as shown below.

Next, select the Blue Square image set and open the Attributes Inspector on the right.

You will see that the Attributes Inspector includes a new On Demand Resource Tags section, which is where you assign tags to each resource. For the blue square image set, enter Blue and Square in the On Demand Resource Tags field. This means that the image set now has two tags assigned to it.

Note that the starter project already includes resource tags for nine of the twelve image sets. This explains why Xcode provides autocompletion options for you when you entered these tags.

Once you have completed assigning tags for the Blue Square image set, add the correct tags to both the Green Hexagon and Red Circle image sets as shown below.

With the on-demand resource tags correctly set up, open to the Project Navigator on the left. Open the Resource Tags tab at the top and select the Prefetched filter at the top.

You can now see how large each asset pack is and exactly what resources are in each one. The All filter shows you each of the on-demand resources. The Prefetched filter shows the on-demand resources per category and it lets you move resources from one category to another:

Initial Install Tags

Prefetched Tag Order

Download Only On Demand

These sections mirror the three categories of asset packs that I outlined earlier. One important thing to note is that the asset packs you put in the Prefetched Tag Order section will begin downloading in the order that they appear in.

With tags assigned to each image set, it is time to start accessing the resources in the project.

3. Accessing Resources on Demand

Accessing asset packs that are hosted on the App Store servers is handled by the new NSBundleResourceRequest class. An instance of this class is created with a set of tags that you want to use. It tells the system about your usage of the corresponding asset packs. The deallocation of these NSBundleResourceRequest objects is the best and easiest way of telling the operating system when you are no longer using a particular asset pack. This is important so that you don't exceed the 2GB limit for resources that are in use.

In your project, open DetailViewController.swift and add the following property to the DetailViewController class.

With this code, you first initialize the request property with a set that includes a single tag. The set of tags you provide to this initializer contains string values. In this case, we use the tagToLoad property, which is set by the previous view controllers in the application.

Next, we begin downloading the asset packs for the specified tags by calling beginAccessingResourcesWithCompletionHandler(_:). This method will access all resources with the specified tags and will automatically start a download if needed. After accessing the resources in this manner, all of your other code for loading these resources into your app remains the same.

Note that if you wish to only access resources that have already been downloaded, without loading content, you can use the conditionallyBeginAccessingResourcesWithCompletionHandler(_:) method.

As shown in the code above, one important thing to remember about this completion handler is that it is called on a background thread. This means that any user interface updates you want to make upon completion will need to be executed on the main thread.

Build and run your app again and choose a color or shape to view in the app. You should see all three colored images for a specific shape or all four shapes for a specific color.

That's how simple it is to use on-demand resources. You have now successfully implemented on-demand resources in an application.

An important debugging feature available in Xcode 7 is the ability to see which asset packs you have downloaded and which ones are in use. To view this, navigate to the Debug Navigator with your app running and select Disk. You will see a screen similar to the one shown below. On Demand Resources is the section that we're interested in.

As an example, let's now change the download priority so that some resources are always downloaded immediately. At the same time, we will change the preservation priorities of the asset packs so that the Hexagon and Star asset packs are purged before the Circle and Square asset packs. Update the implementation of the viewDidAppear(_:) method as shown below.

After initializing the request, we set the loadingPriority property to NSBundleResourceRequestLoadingPriorityUrgent. Alternatively, you can assign any number between 0.0 and 1.0 to this property in order to dictate the loading priority within your app.

The advantage of using this constant is that it automatically gives the request the highest loading priority, but it also disregards current CPU activity. In some situations, if the device's CPU is being used heavily, the download of an asset pack can be delayed.

Next, we set the preservation priority for all four shape tags. This is done by calling the setPreservationPriority(_:forTags:) method the application's main bundle. We've now ensured that, if the on-demand resources system needs to purge some assets from our app, the Hexagon and Star asset packs will be deleted first.

4. Best Practices

Now that you know how to implement on-demand resources in an iOS application, I want to tell you briefly about a few best practices to keep in mind.

Keep Individual Tags as Small as Possible

In addition to reducing download times and making your resources more accessible, keeping each asset pack as small as possible prevents the system from over-purging. This is when the on-demand resources system needs to free up a certain amount of space and ends up freeing a lot more than necessary.

For example, if the system needed to free up 50MB of space and, based on the conditions mentioned earlier, decided that a 400MB asset pack from your app was the most suitable to be deleted, the system would over-purge 350MB. This means that if your app has lost more data than it needed to, it will need to download all of the resources associated with that tag again. The recommended size for individual tags is approximately 64MB.

Download Tags in Advance

If your app has a very predictable user interaction, then it is best to start downloading resources before they are actually needed. This is to improve the user's experience as they then don't have to stare at a loading screen while your app downloads content.

Games are a common example. If the player has just completed level 5, then it's a good idea to start downloading level 7 while she plays level 6.

Stop Accessing Resources Correctly

When you are done using a particular asset pack, make sure that either your NSBundleResourceRequest object is deallocated or you call the endAccessingResources method on it.

Not only will this avoid your application hitting the 2GB limit for in-use resources, it will also help the on-demand resources system to know when your app uses those resources, which means that it can better decide what to purge if more space is needed.

5. On-Demand Resources for tvOS

I recently wrote about tvOS development and in that tutorial I mentioned the limitations of tvOS applications. The maximum size for an app bundle is 200MB so it is highly recommended that you utilize on-demand resources in your tvOS apps whenever possible.

Due to the similarities of tvOS and iOS, the APIs and storage limits (except for the app bundle) for on-demand resources are the same. When working with on-demand resources on tvOS, however, it is also important to remember that all assets, such as images, have a single 1x scale version so the size of your asset packs as shown in Xcode will not decrease due to app slicing.

Conclusion

On-demand resources in iOS 9 and tvOS is a great way to reduce the size of your app and deliver a better user experience to people who download and use your application. While it's very easy to implement and set up, there are quite a few details that you must keep in mind in order for the whole on-demand resources system to work flawlessly without excessive loading times and unnecessarily purging data.

As always, be sure to leave your comments and feedback in the comments below.