On-Demand Resources in iOS Tutorial
On-demand resources are a technique to reduce the initial download size of applications which rely on large assets for use. This is especially useful to games developers with large assets only required during certain stages of the game. By James Goodwill.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
On-Demand Resources in iOS Tutorial
20 mins
- Getting Started
- Examining the App
- Game States
- Starting the Game
- Bundles
- Tag Types
- Assigning and Organizing Tags
- Introducing NSBundleResourceRequest
- Using NSBundleResourceRequest
- Downloading Resources
- Looking at the Device Disk
- Best Practices
- Error Handling
- Loading Priorities
- Purging Resources
- Where to Go From Here?
Note: This tutorial uses Xcode 9 and Swift 4.
iOS 9 and tvOS introduced the concept of on-demand resources (ODR), a new API used to deliver content to your applications after you’ve installed an app.
ODR allows you to tag specific assets of your application and have them hosted on Apple’s servers. These assets won’t be downloaded until the application needs them, and your app will purge resources from your user’s devices when they’re no longer needed. This results in smaller apps and faster downloads — which always makes users happy.
In this tutorial, you’ll learn the basics of on-demand resources including:
- Tagging and downloading individual resources
- Looking at Xcode’s Disk Report to see which resources are on the device
- How to organize resources into different tag types
- Some best practices to make sure your users get the best experience
Getting Started
Download the starter project for this tutorial. You can find it Bamboo-Breakout-Starter.
The starter project is a game called Bamboo Breakout. Michael Briscoe wrote this app as a SpriteKit tutorial, and it serves as a great example of how simple it is to write a SpriteKit app in Swift. You can find the original tutorial here.
The original game had only one game level, so I’ve added a few changes to the original app: five new game levels and some code to load each level.
Examining the App
Once you have the starter application, open it in Xcode and open the Bamboo Breakout folder.
In this folder, you will see six SpriteKit scenes. Each one of these scenes represents a level in the Bamboo Breakout game. At the moment, you’re packaging all these scenes with the application. By the end of this tutorial, you’ll have only the first level installed.
Build and run the app, and you’ll see the first level of the game in the simulator.
Game States
Time to take a look at the starter project. You don’t need to examine the entire project, but there’s a few things you do need to be familiar with.
Look at the top of the GameScene.swift class. In Xcode, open GameScene.swift and look for the following snippet:
lazy var gameState: GKStateMachine = GKStateMachine(states: [
WaitingForTap(scene: self),
Playing(scene: self),
LevelOver(scene: self),
GameOver(scene: self)
])
Here you see the creation and initialization of a GKStateMachine
object. The GKStateMachine
class is part of Apple’s GameplayKit; it’s a finite-state machine that helps you define the logical states and rules for a game. Here, the gameState
variable has four states:
-
WaitingForTap:
The initial state of the game -
Playing:
Someone is playing the game -
LevelOver:
The most recent level is complete (this is where you’ll be doing most of your work) -
GameOver:
The game has ended either with a win or a loss
To see where the initial game state is set, scroll down to the bottom of didMove(to:)
.
gameState.enter(WaitingForTap.self)
This is where the initial state of the game is set, and it’s where you’ll begin your journey.
Note: didMove(to:)
is a SpriteKit method and part of the SKScene
class. The app calls this method immediately after it presents the scene.
Note: didMove(to:)
is a SpriteKit method and part of the SKScene
class. The app calls this method immediately after it presents the scene.
Starting the Game
The next thing you need to look at is touchesBegan(_:with:)
in GameScene.swift.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch gameState.currentState {
// 1
case is WaitingForTap:
gameState.enter(Playing.self)
isFingerOnPaddle = true
// 2
case is Playing:
let touch = touches.first
let touchLocation = touch!.location(in: self)
if let body = physicsWorld.body(at: touchLocation) {
if body.node!.name == PaddleCategoryName {
isFingerOnPaddle = true
}
}
// 3
case is LevelOver:
if let newScene = GameScene(fileNamed:"GameScene\(self.nextLevel)") {
newScene.scaleMode = .aspectFit
newScene.nextLevel = self.nextLevel + 1
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
self.view?.presentScene(newScene, transition: reveal)
}
// 4
case is GameOver:
if let newScene = GameScene(fileNamed:"GameScene1") {
newScene.scaleMode = .aspectFit
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
self.view?.presentScene(newScene, transition: reveal)
}
default:
break
}
There’s a lot going on here. Let’s go through this line by line.
-
When the system invokes
touchesBegan(_:with:)
,gameState.currentState
is set toWaitingForTap
. When theswitch
hits thiscase
, the app changesgameState.currentState
to thePlaying
state and setsisFingerOnPaddle
totrue
. The app uses theisFingerOnPaddle
variable to move the paddle. -
The next
case
in theswitch
executes when the game is in thePlaying
state. This state is used to track when the user is playing the game and touching the game paddle. -
The app executes the next
case
statement when the game is in theLevelOver
state. In this case the game loads the next scene based on thenextLevel
variable. ThenextLevel
variable is set to 2 on creation of the very first scene. -
When the game is in the
GameOver
state, it loads the scene GameScene1.sks and restarts the game.
This process assumes you packaged all these scenes with the installed app.
Bundles
Before you start using on-demand resources, you need to know how resource bundles work.
iOS uses Bundles to organize resources into well-defined subdirectories inside an application. You need to use the Bundle object to retrieve the resource you are looking for; the Bundle object provides a single interface for locating items. You can imagine the main bundle looking similar to the following:
This example shows what the main bundle looks like when it contains three game levels.
On-demand resources are different. They’re not packaged with the distributed application. Instead, Apple stores them on their servers. Your app retrieves them only when it needs to, using NSBundleResourceRequest
. You pass the NSBundleResourceRequest
object a collection of tags, which represent the resources you want to retrieve. When the app downloads the resources to the device, they’re stored in an alternative bundle.
In this example, the application is making a request for three on-demand resources. The system will retrieve these resources and store them in an alternative bundle.
Now, what exactly are tags?
Tag Types
- Initial Install Tags: These resources are downloaded when the app is downloaded from the App Store. These resources affect the total size of the in the App Store.
- Prefetched Tag Order Tags: These resources are downloaded once the app has been installed on the user’s device. The App downloads them in the same order they appear in the Prefetched Tag Order group.
- Downloaded Only On Demand Tags: These resources are downloaded when the app requests them.
Note: You can only use Downloaded Only On Demand while in development. You’ll have to deploy the app to the App Store or TestFlight to use the other tag types.
Note: You can only use Downloaded Only On Demand while in development. You’ll have to deploy the app to the App Store or TestFlight to use the other tag types.