On-Demand Resources in iOS
On-demand resources help to reduce the initial download size of applications that rely on large assets for use. This is especially useful to game developers with large assets only required during certain stages of the game.
How does ODR work?
- Identify on-demand resources during development by assigning them one or more tags.
- At runtime, you need to request access to remote resources by specifying a set of tags. The operating system downloads any resources marked with those tags and then retains them in storage until the app finishes using them.
- When the operating system needs more storage, it purges the local storage associated with one or more tags that are no longer retained.
- Tagged sets of resources may remain on the device for some time before they are purged.
Step-by-step guide to enable ODR:
First, We have to enable the ODR feature in our project:
As soon as we’ve done this, this feature will be available in our app. Before we can start using it, let’s explain how it works.
Imagine We have a main bundle and an additional bundle.
Bundles 1 and 2 are in the main bundle, while Bundles 3 and 4 are in additional bundle. What we want to achieve in this tutorial, is to download all the additional Bundles from Apple’s servers instead of bundling them in the main app bundle.
To achieve the above, we need to use tags with unique tag names for assets with ON Demand Resources as shown below :
We can tag resources for below cases:
When the Bundle needs to be -
- installed on the initial install
- prefetched when the app has been installed on a device
- or downloaded only on demand
After we have tagged our assets, we can then request a resource, identified by its tag, to be downloaded when needed using the below class:-
import Foundation
protocol ODRManagerDelegate: class {
func doneLoading(tag: String, successfully: Bool)
}
class ODRManager {
var delegate: ODRManagerDelegate?
var requests = [NSBundleResourceRequest]()
func load(tag: String) {
let request = NSBundleResourceRequest(tags: [tag])
requests.append(request)
request.conditionallyBeginAccessingResources { (resourcesAvailable) in
if resourcesAvailable {
if let d = self.delegate {
d.doneLoading(tag: tag, successfully: true)
}
} else {
request.beginAccessingResources { (error) in
if let err = error {
self.delegate?.doneLoading(tag: tag, successfully: false)
NSLog("Error accessing resource: %@", err.localizedDescription)
} else {
self.delegate?.doneLoading(tag: tag, successfully: true)
}
}
}
}
}
}
To load or download the ON-Demand Resource assets with the tag we need to use the below code in the ViewController where we want to display it.
let odrManager = ODRManager()
odrManager.delegate = self // If we want to use the above created ODRManagerDelegate
odrManager.load(tag: "gold47")
When the App is hosted in testflight we can see the differences between the App Store File Sizes with ON-Demand Resources Enabled and Disabled as below:
This tutorial is just about taking us through the basics. And as we can see from the above code snippets, we still have to handle errors. Typical errors are:
- NSBundleOnDemandResourceOutOfSpaceError — no space left on device
- NSBundleOnDemandResourceExceededMaximumSizeError — bundle is too big, try to create smaller bundles
- NSBundleOnDemandResourceInvalidTagError — the tag weare requesting does not exist (any more)
When our app doesn’t need the bundles anymore we can cleanup after using the resources using the below snippet.
request.endAccessingResources()
The only downsides we have are:
- uploading to Apple takes a while because everything is bundled
- fixing a bug in a bundled App requires a new app submission to Apple because the bundles are on Apple’s servers, not on our own web servers any more.
References to learn more about on demand resources, check out this WWDC video, read more about it on kodeco.com or have a look at Apple’s documentation.