A new and noteworthy addition to iOS 9 is App Thinning with App Slicing and On Demand Resources.

App Slicing creates different "slices" of your app for specific devices and architectures, thus reducing app size. Apps are downloaded more quickly and have less impact on the limited storage on the device and cellular data limits.

On Demand Resources is another way to save storage on the device and increase download speed. With On Demand Resources only specific assets of the application are downloaded when requested.

With that brief overview let's delve right in. Starting with App Slicing.

App Slicing

The good news is, this will pretty much be automatically handled via the App Store and requires little or no effort on the development side. Compiled binaries will be generated based on the device's architecture, screen resolution, and graphics engine.

There are only two essential things to do in order to start using App Slicing. Images stored in 1x, 2x, and 3x format will automatically be sliced per device according to the device's native resolution. So, your resources must be organized in asset catalogs. Secondly, you should also make sure you have all available architectures (arm64 armv7 armv7s) enabled in your "Build Settings" under "Valid Architectures".

In addition to the standard images being stored in asset catalogs, you can also direct specific files to different devices, such as HTML, fonts, JSON, or pretty much any kind of data file.

Loading data files from an asset catalog is one of the best new features related to App Slicing. You can now load data files from an asset catalog via NSDataAsset. It's relatively simple to do. After adding an asset catalog, press the plus button to add a "New Data Set" then simply drag your data file into the catalog.

You can also specify device specific files by selecting the various devices in the "Attributes Inspector". This will allow app slicing for your data file.

Once you have your data set ready you can then load it in your code as in the example below.

NSDataAsset *colorsJsonDataAsset = [[NSDataAsset alloc] initWithName:@"Colors"]; 
<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/">NSError</a> *error = nil; 
<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/">NSArray</a> *colorsJson = [NSJSONSerialization JSONObjectWithData:colorsJsonDataAsset.data options:kNilOptions error:&error];

Distributed enterprise apps or testing builds not stored in the app store can also have the same app slicing benefits. Xcode, Test Flight, and Xcode Server will all support App Slicing.

On Demand Resources

On Demand Resources is another great way to minimize your app size. You can load app resources on the fly or preload them directly from the App Store. This is great for very large apps, especially ones that you would like to stay within the cellular data download limit.

There are three main categories On Demand Resources..

  1. Initial Install Tags
    • These are resources required for the app to run.
    • They will be automatically downloaded during the initial app download.
  2. Prefetch Tags
    • These will be downloaded during the first run of the app after the app has already been installed.
  3. Download Only On Demand Resources
    • These will be downloaded only when requested by the app.

Although now with On Demand Resources, your app can be potentially much larger, there are still size limitations involved.

  • App binary has a limit of 2GB
  • Initial Install Tags: 2GB
  • Initial Install Tags and Prefetched Tags: 4GB
  • Download Only On Demand Resources (In use on device): 2GB
  • Download Only On Demand Resources (Hosted in App Store): 20GB.

The first step in using On Demand Resources is tagging. Resources are downloaded by the app via their tag. Resource tags can be applied either in "Resource Tags" in the project settings or in the "Attributes Inspector" for each asset in the asset catalog.

You can apply one or multiple tags thereby grouping assets/resources into downloadable packs.

Once your resources have been tagged, you can load these resouces using "NSBundleResourceRequest".

We simply init the bundle resource request with our tags and begin the download via "beginAccessingResourcesWithCompletionHandler".

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
[self.resourceRequest beginAccessingResourcesWithCompletionHandler:^(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/">NSError</a> * __nullable error) {
 // Load Complete
}];

In addition to the standard approach, we can also conditionally load the resources. A conditional load will first check to see if the resources have already downloaded, allowing the developer to not be forced into waiting on the download. This is useful for doing things like packaging the app with low resolution images and downloading high resolution images in the background as needed.

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
[self.resourceRequest conditionallyBeginAccessingResourcesWithCompletionHandler:^(BOOL resourcesAvailable) {
 if(resourcesAvailable) {
 // Resources Downloaded
 } else {
 // Resources not available
 // Do something like load low quality images stored
 // in app.
 }
}];

Using standard KVO, you can also apply progress tracking of the download. The resource request contains a "NSProgress" property which can be used to view the progress of the download, as well as cancel, pause, or resume the download.

- (void)loadOnDemandResourceWithProgress {
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
 self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
 [self.resourceRequest.progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:NULL];
 
 [self.resourceRequest beginAccessingResourcesWithCompletionHandler:^(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/">NSError</a> * __nullable error) {
 [self.resourceRequest.progress removeObserver:self forKeyPath:@"fractionCompleted"];
 
 dispatch_async(dispatch_get_main_queue(), ^{
 // Loaded On Demand Resource
 // Modify UI here as needed
 });
 }];
}
 
- (void)observeValueForKeyPath:(nullable <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)keyPath ofObject:(nullable id)object change:(nullable <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/">NSDictionary</a> *)change context:(nullable void *)context {
 if((object == self.resourceRequest.progress) && [keyPath isEqualToString:@"fractionCompleted"]) {
 // Do something with self.resourceRequest.progress.fractionCompleted
 }
}

When a resource is no longer needed, it's important to call "endAccessingResources" on the resource request in order to let the system know the resources can be purged from memory to free up space.

- (void)viewWillDisappear:(BOOL)animated {
 [self.resourceRequest endAccessingResources];
}

Although this is everything you need to get started with On Demand Resources, there some smaller details that may be useful for the more advanced developer.

A resource request can have a "loading priority" and "preservation priority". These are numeric proiorities between 0.0 and 1.0 that give priorities to the loading of the resources and life of the stored resources.

By lowering the loading priority you can free up memory and increase app speed, thus resulting in lower download speeds. In contrast you can increase the loading priority putting more strain on the user's device but achieving greater download speeds.

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
self.resourceRequest.loadingPriority = 0.0;
// Start Download

If you set the loading priority to urgent, you will get the fastest possible loading but with possible performance decreases in your app.

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
self.resourceRequest.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent;
// Start Download

By setting the preservation priority, you can specify what resources will get purged from memory first in the event the device must free up space.

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> *tags = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/">NSSet</a> setWithObjects:@"colors", nil];
self.resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
[self.resourceRequest.bundle setPreservationPriority:0.0 forTags:tags];
// Start Download
Testing

One of the most important aspects is testing App Slicing and On Demand Resources. It's important to test on all possible devices. You can also use Xcode to generate various slices of the App for testing which is very useful for determining app size per device.

It's easy to create sliced IPA's through the normal archive approach.

First tap "Archive" under the "Product" menu. After creating the archive, tap "Export", choose the export method, and finally choose what slice you wish to make.

On Demand Resources are easily tested using the "Debug Navigator" and tapping on "Disk". There's a section found toward the bottom that will list your On Demand Resources via tag and show their size and status, such as "Downloaded" or "In Use".

Conclusion

There you have it! App Thinning in iOS9. Everyone should take advantage of App Slicing but of course only with thorough testing on as many devices as possible. On Demand Resources may or may not be useful based on the size and type of app you are creating but are certainly worth consideration. A small app with limited resources wouldn't see much benefit, but a large app with many resources (such as a game) could easily benefit from loading resources on demand. When using On Demand Resources, think closely about the user experience. Create a minimal storage footprint on the device, preload resources so they can be provided as requested, and purge resources that are no longer needed. With some thought, On Demand Resources can reduce your app size and still give a seamless user experience.

Hopefully this small tutorial has helped you start reducing the size of your app while increasing performance.