blog

Photo of someone weighing themselves by i yunmai on Unsplash

The Skinny on iOS App Thinning

by

What’s the best type of refactoring you could ever perform on source code? Silly question, right? The best answer I’ve seen is to delete some of it.

When you’ve been debugging for a while and it occurs to you that the code in question doesn’t even need to exist, it’s sort of a thrill. Experienced engineers and UI/UX designers have a joy in deleting things that is hard for people new in software to grasp.

And on that note: for me, one of the most exciting WWDC announcements about iOS 9 is App Thinning.  As new iOS device models arrive with updated hardware, my universal apps have accrued binaries and graphics resources to take advantage of the newest devices.  Up to now I’ve tolerated that every end user has to download the whole collection, even the parts they will never use.  Not anymore!

What is App Thinning?

Starting with iOS 9, Apple will provide apps stripped of unused resources by target device in the following areas:

  • Images by Retina @1x/@2x/@3x scale
  • Binary code by architecture
  • App-defined “On Demand Resource” files

The way it works is that the App Store will start sending out customized app packages via "App Slicing" that include exactly what each end user needs for their device, leaving out the rest.

What’s In It For Me?

So we know it’s The Right Thing To Do.  People with 16 GB (or heaven forbid, 8 GB) of storage on their iDevices would see a profound difference if all app developers applied App Thinning principles.  Unfortunately, that’s not likely to happen.  You could call it the "Tragedy of the Commons" or perhaps the "Tragedy of Development Budgets".

It’s certainly easy to follow an App Thinning-compatible path for new development, as we’ll explore in this post.  In terms of reworking existing applications, not every app will see significant benefit, so you have to estimate the value case by case.

There are some existing apps for which the benefits will make this an easy call, for example games and other multimedia intensive apps.  These are the ones that show up at the top of the Storage settings applet sorted by space consumption.  Your end users are going to be staring with narrowed eyes at your app’s name every time their device runs low on space, making a fresh decision about whether to delete your app.  Nobody wants to be at the top of that list.

Low Hanging Fruit: Asset Catalogs

The old way to specify images for different device profiles was to provide suffixes on your image file names like "~ipad" and "@2x" to indicate device support.  In my experience this was sometimes a little flaky and unreliable.  More importantly, the fallback logic made it forgiving of holes in your coverage, which becomes even more critical now that we are talking about selectively not including some resources.  The use of Asset Catalogs removes all this ambiguity.

I have a confession to make. Some of my apps don’t use Asset Catalogs for their image resources, because I originally saw no value in it.  It seemed kind of silly to have a separate folder and JSON file for each logical image in an app.  With App Thinning being based on Asset Catalogs, I’ve changed my mind and now use them across the board.  This will enable the App Store to deterministically and safely exclude irrelevant resources in a "slice" of your app for the target device.

The structure of an Asset Catalog is pretty clear, which is great for me since I often generate my assets via Python/PIL scripts.  Each Asset Catalog folder has a file named Contents.json and a bunch of PNG files as follows:

{
   "images" : [
      {
         "idiom" : "iphone",
         "scale" : "1x",
         "filename" : "info_1x.png"
      },
      {
         "idiom" : "iphone",
         "scale" : "2x",
         "filename" : "info_2x.png"
      },
      {
         "idiom" : "iphone",
         "scale" : "3x",
         "filename" : "info_3x.png"
      },
      {
         "idiom" : "ipad",
         "scale" : "1x",
         "filename" : "info_ipad_1x.png"
      },
      {
         "idiom" : "ipad",
         "scale" : "2x",
         "filename" : "info_ipad_2x.png"
      },
   ],
   "info" : {
      "version" : 1,
      "author" : "xcode"
   }
}

There are also Asset Catalogs related to icons and splash images which have different JSON elements.  The easiest thing to do is to use Xcode to create and populate each Asset Catalog.  Then if you use scripting as I do, one straightforward technique is to (manually) populate your Asset Catalog in Xcode with placeholder images, let’s say just solid magenta rectangles.  Then your script only needs to replace all those images with your production assets, and it should be easy to see which ones were missed.

For the Brave: On-Demand Resources

The next logical step of app thinning is to go beyond optimization by device model, and strip resources that might be unused for any reason.  There may be storage consumed by multimedia files that you don’t think the user will access in the near term that can safely be excluded.  Obviously it is up to you to make a safe determination there.  An example might be for a game that includes multiple sets of levels, or perhaps a guitar tutorial that has multiple semesters of content.  If the user is unlikely to need a segment for weeks or months, there’s little point in having it downloaded all the time.

The challenge here is to avoid causing frustration to users who may attempt to access to the content either without internet access, or with insufficient bandwidth to download large resource packs, such as while flying on a typical airline.  As such, this is a more risky technique than optimizing by target device, and I’d suggest finding ways in your UI to be open with users about what is and is not downloaded to clue them in to possible unpleasant surprises.

To implement On Demand Resources, you must designated files or set of files under custom defined categories, and then implement logic to request those resources when they become necessary.  Here is a link to a video describing On Demand Resources, along with Apple’s On Demand Resources development guide.

Leave it to Apple: Generating Bitcode

iOS developers are accustomed to dealing with updated architectures like armv6, armv7, armv7s.  When new iPhones and iPads come out, Apple frequently updates the physical processor on the device, adding support for a different instruction set.  Then you have to either include an extra binary application image in your package, or else forego available optimizations and rely on backwards compatibility, like for example having an arm7s device run arm7 code.  Much of this happens automatically such that all you need to do is edit the target architecture field, but it’s still undesirable to bundle multiple binary executables in your app.

With the new Bitcode compilation option in Xcode 7, the App Store becomes responsible for the final step of compiling your app for the destination device’s architecture.  So I only need to compile one app binary, which will be in an LLVM bitcode or intermediate format, and Apple will take care of the rest.  No longer do I need to include multiple copies of the executable binary for each targeted processor architecture.

One reason this is exciting to me is that at least in theory, it could make it easier to continue to support older devices without having to maintain old copies of Xcode and possibly go through a complicated dance to build the app package.  Just let Apple build all the permutations.

Note that Bitcode is mandatory for apps submitted for the Apple Watch OS.  I expect it will eventually become mandatory for iOS apps as well, but presumably Apple will first give devs a grace period to work through the pain of the transition, and third party vendors to provide bitcode versions of their libraries.  It appears at least that Apple is strongly pushing people in this direction, since when you enable the flag to embed bitcode in your app, it will refuse to build unless all dependency components also provide bitcode.

Less Flashy But Just as Important: Caching

As exciting as the official app thinning announcements are, all this use of device-specific resource slicing and bitcode based custom builds won’t help you if you are irresponsible with the caching of downloaded or generated resources.  Do you download multimedia content from the internet, such as user avatar images or tutorial videos?  Do you generate files on disk related to user created content?  Take time to audit your app’s use of caching and make sure you are deleting files when you’re done with them.

As a regular iOS user, I can attest that at least as of a few years ago, a prime offender here was Apple themselves; some online forums suggest others have found the same: when a user would rent a movie on iTunes, the app would (sometimes) fail to delete the cached video after the user was done with it, even when the device was rebooted and no visible entry of the film title remained.  When I discovered this situation on my own device, I ended up using an external file explorer to find and destroy the offending file.  Video caching is probably the most extreme example here since it can easily subtract a gigabyte or two from your device’s available storage.

Just as important as deleting files when you are done with them is the principle of deleting files that you failed to delete before.  You can’t predict when the user will kill your app or a crash will occur, which can foil your attempt to clean up elegantly.  More subtly, the user may install a new version of your app, and your logic may not handle the transition in cache scheme versions cleanly.  Just be sure you have a plan for purging old files.

If you follow the techniques above it will benefit both your end users’ experience and your professional reputation.  At the same time, you will gain that special satisfaction that can only come from "deleting" chunks of your own software.  It’s a great feeling.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more