Accessing and Downloading On-Demand Resources
An app uses NSBundleResourceRequest
to:
Request access to on-demand resources
Inform the operating system when access is no longer needed
Update the priority of a download
Track the progress of a download
Check for a notification of low disk space
You use other methods in NSBundle to set the preservation priority in local storage of the downloaded resources.
Requesting Access
The app must request access to a tag before using any of the tag’s associated resources by initializing an NSBundleResourceRequest
instance. The first step is to create an NSBundleResourceRequest
object for the tags. A tag can be managed by multiple NSBundleResourceRequest
objects.
Initializing an NSBundleResourceRequest Instance
Each instance of NSBundleResourceRequest
manages a set of tags that are in the same resource bundle. For more information on bundles, see Bundle Programming Guide. You set the managed tags and their bundle when the instance is initialized using one of two methods:
Use
initWithTags:
if all of the resources associated with the tags are in the main bundle.Use
initWithTags:bundle:
if all of the resources associated with the tags are in the same custom bundle.
Listing 4-1 shows an example of initializing a resource manager for managing access to the on-demand resources that are tagged with birds
, bridge
, or city
. The tags and their associated resources are configured in Xcode as described in Creating and Assigning Tags. All of the resources are part of the main bundle.
Listing 4-1 Initializing an NSBundleResourceRequest
instance
// Create an NSSet object with the desired tags
NSSet *tags = [NSSet setWithObjects: @"birds", @"bridge", @"city"];
// Use the shorter initialization method as all resources are in the main bundle
resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
Requesting Access to the Tags
After initializing the instance, the next step is to request access to the on-demand resources that are associated with the tags. When all the resources for the requested tags are in local storage, the operating system retains them and uses a callback to inform the app that they are available. For more information, see stage 6 of the On-Demand Resource Life Cycle.
You can use one of two methods to request access to resources. The main difference between the methods is how they respond if the resources are not on the device:
beginAccessingResourcesWithCompletionHandler:
downloads the resources from the App Store.conditionallyBeginAccessingResourcesWithCompletionHandler:
does not download the resources.
Each method takes a callback block that is called with the result of the access request. All of the associated resources will be on the device when the callback is invoked with a successful result.
Listing 4-2 shows beginAccessingResourcesWithCompletionHandler:
. The callback block sets a flag that indicates whether the resources were downloaded. Your code should handle any errors including presenting feedback to the user. If there is no error, the app can start accessing the on-demand resources.
Listing 4-2 Using beginAccessingResourcesWithCompletionHandler:
// Request access to the tags for this resource request
[resourceRequest beginAccessingResourcesWithCompletionHandler:
^(NSError * __nullable error)
{
// Check if there is an error
if (error) {
// There is a problem so update the app state
self.resourcesLoaded = NO;
// Should also inform the user of the error
return;
}
// The associated resources are loaded
self.resourcesAvailable = YES;
}
];
The callback block is not called on the main thread. Any actions that access the user interface, such as presenting an error, must be dispatched to the main thread for execution. For information on how to dispatch calls to the main thread, see Concurrency Programming Guide.
Checking Whether Tags Are Already on the Device
conditionallyBeginAccessingResourcesWithCompletionHandler:
grants access if the tags are already on the device. If the tags are not on the device, the app needs to call beginAccessingResourcesWithCompletionHandler:
to download them. Listing 4-3 shows an example of checking whether resources are already on the device. The callback checks whether the textures and graphics for an area of a world are loaded. If so it continues. Otherwise it calls another routine to load the resources.
Listing 4-3 Using conditionallyBeginAccessingResourcesWithCompletionHandler:
// Request access to tags that may already be on the device
[resourceRequest conditionallyBeginAccessingResourcesWithCompletionHandler:
^(BOOL resourcesAvailable)
{
// Check whether the resources are available
if (resourcesAvailable) {
// the associated resources are loaded, start using them
self.loadNewWorldArea = YES;
} else {
// The resources are not on the device and need to be loaded
// Queue up a call to a custom method for loading the tags using
// beginAccessingResourcesWithCompletionHandler:
NSOperationQueue.mainQueue().addOperationWithBlock(^{
[self loadTags:resourceRequest.tags forWorldArea:worldArea];
});
}
}
];
When to Request Tags
Request tags before you need them because it takes time to download the associated resources from the App Store. For example, if the user is playing level 1, start downloading level 2 so that it is loaded by the time the user has finished level 1.
The amount of time depends on several factors including the size of the associated resources and the speed of the connection between the device and the App Store.
In an ideal case with no restrictions on bandwidth or processing power, a tag with a size of 64 MB would take at least 1.7 seconds to download over a 300 Mbps 802.11n Wi-Fi or LTE cellular phone connection (peak LTE is 299.6 Mbps). However, in the real world, the amount of data that can flow over a connection to the Internet is far slower than 300 Mbps. In addition, the operating system usually uses a small amount of system resources for the download, resulting in longer transfer times.
You should test downloading speeds under various network conditions on the kinds of devices you expect your customers to use. Use the download speeds from the testing to estimate download times.
For more information, see Designing for On-Demand Resources.
Downloading and Downloading Priority
The loadingPriority
property of a resource request is used by the operating system as a hint for prioritizing the order of your download requests. Requests with a higher value for loadingPriority
are loaded before ones with a lower value.
Resource requests have a default priority that can be changed at any time, including during the download. Listing 4-4 shows an example of the code for changing the priority of a request.
Listing 4-4 Changing the priority of a download
// The priority is a number between 0.0 and 1.0
self.resourceRequest.loadingPriority = 0.1;
If the download is urgent, the app can use NSBundleResourceRequestLoadingPriorityUrgent
for the loading priority. This tells the operating system to download the content as quickly as possible. One use for the constant is when the user must wait for the download before doing anything else. Listing 4-5 shows an example of setting the priority as urgent if the user is waiting.
Listing 4-5 Raising the priority of a request
// Raise the priority based on the urgency
if (self.userWaiting) {
// The user is waiting, request the fastest download time
self.resourceRequest.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent;
} else {
// Set a higher priority
self.resourceRequest.loadingPriority = 0.8;
}
Tracking Download Progress
Soon after a download begins, the resource request starts updating progress
, a property of type NSProgress
. The app tracks download progress using key-value observing (KVO) on the fractionCompleted
property of progress
. This requires that you start and stop observation and add code that executes when the value changes. Listing 4-6 shows how to start and stop observing the progress of the download. Listing 4-7 shows executing code when the value changes.
Listing 4-6 Start and stop tracking download progress
// Start observing fractionCompleted to track the progress
[self.resourceRequest.progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:@"observeForrestLoad"
];
// Stop observing fractionCompleted to stop tracking the progress
[self.resourceRequest.progress removeObserver:self
forKeyPath:@"fractionCompleted"
context:@"observeForrestLoad"
];
Listing 4-7 Executing code when fractionCompleted
changes value
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Check for the progress object and key
if ((object == self.resourceRequest.progress) &&
([keyPath isEqualToString:@"fractionCompleted"])) {
// Get the current progress as a value between 0 and 1
double progressSoFar = self.resourceRequest.progress.fractionCompleted;
// Do something with the value
}
}
Two important uses for tracking the download are:
Adjusting the download priority. The download priority can be raised if the download is taking too long, and lowered if there is lots of time.
Providing feedback on progress to the user. Feedback uses the value of
fractionCompleted
.
Pausing and Canceling the Download
You pause, resume, or cancel the active download for the resource request by using the progress
property and the methods provided by NSProgress
(see Listing 4-8). For more information on the methods, see NSProgress Class Reference.
Listing 4-8 Pausing, resuming, and canceling the current download
// Pause the current download
[self.resourceRequest.progress pause];
// Resume the current download
[self.resourceRequest.progress resume];
// Cancel the current download
[self.resourceRequest.progress cancel];
Ending Access
Ending access when the app has finished using a resource request allows the operating system to release the storage on the device. If there are no other resource requests using the same storage, the operating system can reclaim the storage if needed. For more information see stages 8 and 9 of On-Demand Resource Life Cycle.
There are two ways to end access:
Send
endAccessingResources
to the resource request, as shown in Listing 4-9.Deallocate the resource request.
Listing 4-9 Ending access to requested tags
// End access to the managed resources by calling this method
[self.resourceRequest endAccessingResources];
After endAccessingResources
is called, the resource request cannot be used again to request access. If the app needs to access the same tags, it must allocate another instance of NSBundleResourceRequest
.
Setting Preservation Priority
The preservation priority of a tag provides a hint to the operating system about the relative importance of keeping the associated resources in local storage. When the operating system needs to purge tags, it starts with the lowest preservation priority. You can set a high-preservation priority for tags that contain resources that are more important than others such as an in-app purchase, or resources for functionality that is used more frequently.
Preservation priority is set and checked using methods added to NSBundle
as show in Listing 4-10.
Listing 4-10 Checking and setting preservation priority for a tag
// Check the preservation priority for the an in-app purchase module
double currentPriority = [[NSBundle mainBundle] preservationPriorityForTag:@"iap-llamas"];
// Set the priority to the maximum of 1.0 (the default is 0.0)
// Create a set of tags used in the call to set priority
NSSet *tags = [NSSet setWithArray: @[@"iap-llamas"]];
[[NSBundle mainBundle] setPreservationPriority:1.0 forTags:tags];
Low-Space Warning
The operating system sends out the NSBundleResourceRequestLowDiskSpaceNotification
notification if it is unable to free up enough storage space for the current resource request. Your app should stop accessing any tags that are not required, as described in Ending Access above. The app can be terminated if the operating system is unable to free enough space.
In the simple example of a game with multiple levels, the user is in level 4, and the app requests tags for levels 3, 5, and 6. When a low-space warning occurs, the app can release the tags for levels 3 and 6. Listing 4-11 shows code to register for the low-space notification. Listing 4-12 shows a routine that releases tags that are not required.
Listing 4-11 Registering for the NSBundleResourceRequestLowDiskSpaceNotification
notification
// Register to call self.lowDiskSpace when the notification occurs
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(lowDiskSpace:)
name:NSBundleResourceRequestLowDiskSpaceNotification
object:nil
];
Registration for the notification is usually done by the app delegate or master view.
Listing 4-12 Notification handler for low disk space notification
// Notification handler for low disk space warning
-(void)lowDiskSpace:(NSNotification*)theNotification
{
// Free the lower priority resource requests
for (NSBundleResourceRequest *atRequest in self.lowPriorityRequests) {
// End accessing the resources
[atRequest endAccessingResources];
}
// clear lowPriorityRequests preventing multiple calls to endAccesingResource
[self.lowPriorityRequests removeAllObjects];
}
lowPriorityRequests
is not part of any class provided by the operating system. It is a mutable set that that the app needs to create and maintain.
Platform Sizes for On-Demand Resources
Copyright © 2018 Apple Inc. All rights reserved. Terms of Use | Privacy Policy | Updated: 2017-01-12