Reducing the size of iOS app binaries
blog.halide.cam
blog.halide.cam
Taking another look at the payload showed that the otherwise innocuous launch images were 32ish Mb in size. The background of the images wasn't a solid colour. The background pattern was more of grayscale static similar to an unwatchable terrestrial TV feed. It was intended to resemble the initial sound of a CB radio used by truckers. (The "Chshhh" in "Chshhh 10-4 Good Buddy")
Since "staticy" audio/imagery like that is inherently random, each launch image(1 per device resolution) was composed of primarily uncompressable data. Switching out of the "staticy" background for a plain gray reduced the payload size to ~20M, a savings of ~30Mb. That change took approximately an hour.
The takeaway here is to take a good inventory of the payload before tackling size reduction. Otherwise you end up fighting the hard fight just to be pennywise
Also, uncompressable launch images for iPad Retina displays are stupid huge.
I know this might sound somewhat obvious in retrospect, but you could've retained something similar to the original background and reduced the size significantly by generating it at runtime --- the code to create a random-looking background may only be not more than a few dozen bytes, and can be the same for multiple resolutions.
For more examples of this technique, see demoscene productions --- the following video was generated by a binary of less than 4096 bytes:
https://www.youtube.com/watch?v=jB0vBmiTr6o
Source code: https://github.com/in4k/rgba_tbc_elevated_source
Explanation: http://www.iquilezles.org/www/material/function2009/function...
Then again, like most demosceners, I see "you can't" as a challenge... and immediately thought of whether exploiting the image format/whatever loads it to run the necessary code would do the trick. Probably wouldn't please Apple for sure. ;-)
edit: I'm not entirely sure if it's possible to do something like this using launch screens.
Improving the performance of anything: much the same. (Caveats apply, of course. Sometimes using such-and-such a library can genuinely make things faster than hand-rolling an equivalent.)
Improving anything: actually think about things. Care about them.
(There are trade-offs with everything, of course. And time is often a major one. When you see something you want to do but don’t have time for it—“Alas for the wailing of the gulls!”)
1. Wrote to disk the entire payload actually required/retrieved for the user to be both authenticated and invoke meaningful business logic.
2. Compress each component in a manner representative of the transfer medium. Save them in a separate directory tree as the uncompressed data.
2a. If you aren't using any compression in your payload, apologize to your users and start using it. Return to Step 1.
3. From here you can use standard filesystem tools to view payload composition by media type, compressibility, etc.
4. Identify the largest consumers of space in the payload and research how to reduce their size.
5. Implement reductions identified in research.
These steps should really be all you need in most scenarios.
Also see if there are any tools for your environment specifically made to find issues, unused assets and so on. This allowed me to easily remove 200+ unused images yesterday. All in all I removed and compressed 18mb of unused images and files and counting.
If anyone is wondering, since it is related to iOS as well, I used Slender. Highly recommended but use with care, since it has false positives and false negatives. I double checked almost all files it found.
This is actually surprisingly common, much more so these days with cookie cutter repositories.
There are large communities of developers who feel better with a large toolkit of functionality in a build environment, where a typical project may only benefit from a fraction of the included libraries
My limited experience of iOS dev suggests this is less of a problem Here, but I’m sure still is a factor
In my experience, these tend to be the same type of developers who tend to copy-paste code extensively and not really understand what they're doing. They may be really good at finding these "cookie cutter" solutions and following instructions, but when asked to actually explain, they're unable to. This is particularly problematic for debugging, when you ask one of these developers about the reason for the way something is written and he doesn't know beyond the fact that he saw and copied it from somewhere.
If you take a closer look and think about it you often easily spot some no brainers like simply applying compression or reduce the quality of media assets to something that actually makes sense.
Helps with any situation in life!
You won't know what to optimise if you don't know what needs optimising.
Thinking hard is a necessary second step, of course.
They mention not doing A/B testing and that sort of things because it's "their philosophy". Obviously for their size it doesn't make sense to spend a week A/B testing to possibly increase usage by 0.5%, but for FB any change like this represents millions of USD.
The Facebook app doesn't seem to do anything that can't be done in a web view besides notifications. In fact, I usually use the mobile site instead of the native app. The whole point of the total rewrite that they did a few years ago was to make it more efficient. They seem to have forgotten that.
* Yes I'm aware of the Dunning-Kruger Effect (http://rationalwiki.org/wiki/Dunning-Kruger_effect)
People don't seem to realize how big a megabyte actually is. Just look at the size of oldschool games (ex : 8MB for Super Mario 64), or what size coders manage to archive now (see demoscene 64k/4k intros).
And you might say that it was the past, things are much more complex now, blah blah blah... Well, it's partially true but modern platforms allow savings that we couldn't have back then. Realtime vector graphics, better compression algorithms. And considering the trend towards minimalist graphics as opposed to real-life images, assets shouldn't exceed a few kilobytes.
Libraries? The OS has plenty of libraries and assets you can use for free, this should be another size saving compared to the old times. And if you insist on packing your own libraries, I suspect sizes would be much smaller if development tools were smart enough to get rid of everything not needed.
Essentially, the real reason apps are so huge nowadays is because we have enough storage space to be lazy.
Exactly. In the early days of gaming, if you came up with creative ways to pack more levels, graphics, or characters into that same 64k of RAM or 2MB of ROM, you'd likely end up with the more advanced game and sell more copies. Pushing hardware to it's limits was the pride and joy of programmers back then.
The barrier to entry is so much lower now. Anyone can create a phone app. All you need is a Mac and watch some YouTube tutorials, that's why 90% of the App store is garbage.
In the old days you have to have development kits and licenses and you would need to have an actual idea/product, expensive hardware to test and develop on, etc. And I'm really doubting that there were many arcade or console programmers that weren't "clever". Sure, there were bad games back then but mostly do to bad design, graphics, horrible movie license tie-ins...
Talk about a false dichotomy around A/B tests. You can do big things _and_ do small things.
But I guess a lot of people like having the moral high ground of not following trends... they could really just say "we don't feel the need for A/B tests".
I wonder if it would help to have a checkbox "This update is not worth pushing to already installed devices."
At one wwdc delta updates were mentioned (this would solve this issue) but it seems that it went to the pile with open source FaceTime.
Nit: advocating AutoLayout to me doesn’t seem to compute with avoiding libraries. Is it actually feasible to use AutoLayout in a larger-scale app without something like Cartography[1]? Interface Builder still feels quite buggy in this regard.
[0] HN discussion: https://news.ycombinator.com/item?id=14207752
That said, if you decide to bundle a valuable library, it's not the end of the world. We got away with zero libraries, but we're very much an exception.
The issue is that too many teams default to, "Yes, let's bundle this giant library," even they're dealing with a trivial problem. The default should be, "No," and you should have to argue for bloat.
You can now do this: addConstraint(button1.rightAnchor.constraint(equalTo: button2.leftAnchor, constant: -12.0))
I think this is pretty compact and readable as it is. But it's only supported in iOS 9 and macOS 10.11 (right now, only one version back).
BTW there's a totally different story with Android apps that will be installed in developing countries, by users with cheap devices and low connectivity. In that case I would scrape every last bit off. On an iPhone I do expect most users to have a fairly decent connection because you won't get very far without it.
They won't care... until their 16GB iPhone 6 tells them they're out of space, with that convenient action-button that takes them to the Storage section in Preferences, with all the installed apps sorted into a neat list ranked by how much space they take up. You don't want to be high on that list if your app is not absolutely indispensable.
(Heck, sometimes when it happens I even delete the Facebook app; I don't use it often enough to justify 400MB. Lately it seems they've made its presence required to refresh the OAuth pairing of the Messenger app, though...)
Then why does it matter? What was going to bring them back?
Notifications you try to lure them in with just drive them away, after all.
1. purpose-specific utilities (track a flight, track a package, call a taxi, etc.);
2. chat apps that I have no presence on myself;
3. casual games.
I come back to a utility app when I next need to do the thing it's for. But if that's infrequent enough, I might delete the app in the mean-time, and then reinstall it when I next need to do the thing. But given that I'll already be in the app-store, I might look for a competitor instead. At that point, your app's size has churned me.
I come back to a chat app if-and-only-if someone gives me their username on such a chat app as their only contact information, and I really want to talk to them. (Think: dating.) Otherwise, I delete these pretty much as fast as I can, because in aggregate the sheer number of these I accumulate takes up a lot of space no matter how small they each are individually. (And often they're not small individually.)
I come back to a casual game when I'm bored and want to kill time. Casual (i.e. "five minutes at a time") games are roughly substitute goods for one-another, though, so it doesn't matter if I delete any individual one as long as I have some on my phone. (Or a book to read. Either-or.) These are usually the apps where the actual size is the most relevant.
Facebook can get away with it due to being so central to people's lives. Even though 32GB is the minimum storage on new iOS devices now, there are still a lot of people with 16GB iPhones and iPads. Managing storage on these devices is quite painful, with a lot of apps now taking between 50 and 500MB. When I still had a 16GB iPhone, I had to remove many applications because I ran out of space all the time since about a year ago.
We should feel collectively responsible for all this bloat. Sure, we don't have to fit a full Pascal IDE and compiler on a 1.44MB disk anymore. But it is certainly good to keep some reference points in mind:
- Turbo Pascal was a full IDE and was 2.29MB for all the bits. They didn't use an existing GUI framework either.
- QNX fit a kernel + graphical environment on a 1.44MB 3.5" disk, with a network stack and web browser.
213MB for a chat app is outrageous, no matter how you look at it [1].
[1] https://itunes.apple.com/us/app/snapchat/id447188370?mt=8
Forgetting about Turbo Vision? :)
I used to carry MS-DOS 3.3 and Turbo Pascal on a 1.44MB 3.5" disk, back on those days.
Don't remember if it was TP 3, or if I tailored it to my purposes though.
Facebook gets away with its behavior because it's Facebook. For everyone else, it's an uphill battle to get installs.
As an experiment, Segment purchased a successful calculator app, and then deliberately bloated it over time. Installs dropped as the size increased. When it crossed the 100mb cellular download limit, it dropped by 66%.
https://www.recode.net/2016/10/4/13151432/app-size-calculato...
If that 30mb doesn't push you over the 100mb limit, you can expect it to cut installs by 13.5%.
The quicker you can get me from starting the download to launching the app the less time I've got to find another app.
str := "World"
fmt.Printf("Hello %s\n", str)
Then the compiler pulls in the entire fmt library, which can render all kinds of datatypes as part of Printf(); there are even reflection-based format strings like %#v which generates a Go literal for the value that you pass in. Without knowing how Printf() works, the compiler cannot understand that I'm never going to visit these branches of the Printf() function, so it cannot tree-shake these (or any of the functions called by these branches).Because you're not Facebook. When people start seeing space run out on their device and go to settings and see which apps are taking up the most space, Facebook is probably one of the last apps that they will delete. Your large footprint app will be very tempting to delete.
Because while some might tolerate FB, they might not you. And when they get that "storage full" message, your app will be one of the first out (I hope so, it sounds like it would be well deserved).
Have some respect for your users...
Even Linux distro devs don't : Fedora's DNF package manager systematically downloads a file of 30 MB that (I assume) contains the signature of all packages to compare it to my local packages. Even if there are no updates, it still takes 10 min and slows my connection speed.
This boggle my mind, I would have thought from all OS Linux would be the one to save bandwidth.
One thing is worth mentioning here: The package manager could easily save bandwidth if it didn't download the whole package list and instead queried the server only for updates for the set of packages that are actually installed (which is usually an order of magnitude less than what's in the repos). However, this approach is not feasible for most Linux distributions since they rely on mirrors to distribute packages, and mirrors only serve static content.
Not static per user, but basically giving (source + diff + diff) on a daily / weekly / monthly basis allows you to only download a diff based on the frequency which you connect.
Hell, at this point, just git-clone a repo/data structure (potentially with an initial shallow clone since you may not care about history) and roll forward "as good as git"
Understanding how your features/changes affect key metrics and understanding how your users are interacting with your app is so crucial when you are trying to generate revenue.
I would argue Apples analytics are no where near enough to accomplish this.
If he had a 20kloc objective-c app, how much smaller would of it been ;)
Personally I don't like cocoapods and if it were my project I would remove it (and/or not use it in the first place). This is more of a personal preference though, and not necessarily the right thing for other people's projects.
I will ask pngcrush out on a date, take it out to a nice restaurant and then marry it!!
Thank you for the great tips!
If Apple started to charge for bandwidth transparently at market price, that would make a lot of difference. But right now if an app is within the Over-the-Air limit of 100MB, developers don't really care.
Look at Apple's own best practices. No incremental updates, zip compression when brotli and zstd already there, forcing its own pngcrush which just hurts compression without any real benefits for loading time and now it's a mandatory for Asset Catalogs and App Thinning.
I personally analyzed a number of apps and found image assets can be reduced by 30-50%. Not only caring, working on the solution.