Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Reducing the size of iOS app binaries (halide.cam)
117 points by tambourine_man on June 23, 2017 | hide | past | favorite | 81 comments


Circa 2014(15?), I spent a couple of weeks working on reducing a Universal iOS App's(Trapster) payload size. This was before Asset Catelogs, so everything was in a fat binary. We converted the audio to be monoaural. We did PNG-crush. Gzipped embedded dynamic data that was preloaded. Optimized SVGs. All the hoops, they were jumped through. It only reduced binary size from 60Mb to 50Mb.

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.


Switching out of the "staticy" background for a plain gray reduced the payload size to ~20M, a savings of ~30Mb.

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...


You can't programmatically generate the launch image on iOS.


I've never worked with iOS but given what I've heard about the extensive restrictions on what apps can do, I'm not surprised. I guess this is more like an icon which the OS retrieves from a known place and displays, before the app even runs?

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. ;-)


I think it's instead brilliant that they are giving developers the opportunity to display an image that gives user feedback while the app is loading its binary, executing static constructors, dynamically linking libraries, etc. you are not forbidden to roll out your own splash screen screen, but it will not be as immediate and smooth as the builtin splash screen support.


Use an obscure format with a code execution vulnerability in imagemagick to run the generator.


PNG only, and I think certain types of PNG as well (e.g. you can't use the alpha channel).


iOS most certainly does not use ImageMagick to generate this images.


That wasn't an option before iOS 8, I think.

edit: I'm not entirely sure if it's possible to do something like this using launch screens.


Since iOS 8, you can use storyboards to generate launch images at runtime


They still aren't "full" view controllers, though. I don't think any of the controller callbacks are executed. I would be surprised if custom drawing is possible. This feature mostly seems to let you leverage autolayout and size classes, which is probably enough for most people


Yep, that's basically it. You can't link any code to it. I think it was added just to make your launch screen look as similar as possible to your app's first screen.


Reducing the size of anything: websites, apps, you name it: just think about it, pay attention to shrinking easily-shrunk things, and avoid including unnecessary stuff.

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!”)


"Actually think about it" is not a very actionable recommendation though. If I have a big application and I want to make it smaller, it'd be more helpful if you gave me tips how to find spots where I should focus my attention and some hints about how to avoid "unnecessary" stuff. Developers don't include things just because it looks nice in the makefile. Everything solved some problem when it was included.


“ Developers don't include things just because it looks nice in the makefile”

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


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

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.


Caring about bloat is a huge first step. I think developers very often include things because they look nice or more often simply because it's the easy way to go. It takes effort to shrink an image, looking for a smaller library instead of using just a tiny part of that huge go to thing, stripping unneeded code or simply thinking twice about the benefit of including something at all. Also they often don't feel the pain points like their users feel because they tend to develop and test on higher end devices with a fast, unmetered network connection.

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.


Download a VISUAL file manager, seeing your project in chunks letting you focus on the big areas makes it easy to see where to start.

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.


My advice is to follow these steps:

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.


Pro tip: Just Do The Right Thing(tm)!

Helps with any situation in life!


More important than thinking about it is profiling it.

You won't know what to optimise if you don't know what needs optimising.

Thinking hard is a necessary second step, of course.


Not mentioned: their product is incomprehensibly simpler than FB. Besides having 100 times less features, they probably skipped all the a11n, i18n, multitasking, etc...

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.


Still, 400MB is just huge. To put it into perspective, the entire storage of the Nexus One is 512MB. The current Facebook iOS app is larger than an entire smartphone OS with the 2011 Facebook app installed.

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.


>> 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...


How complicated should the FB client side app be * ?

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)


Slightly off topic but I wonder if the name is likely to cause issues given that there's another, possibly older project named Halide in the image processing software world: http://halide-lang.org/ (~6 yrs old according to Github commit history).


Also used heavily in mobile photography, by Google and others


Relevant: The Size of iPhone's Top Apps Has Increased by 1,000% in Four Years

https://news.ycombinator.com/item?id=14616536


That makes me think if all these stuff happens because of developers/requirements, or because of Apple/XCode build routine.


> It’s just our philosophy around products. Knowing too much data warps your mind. You find yourself optimizing for a local maximum, instead of making big bets that really move the needle.

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".


Not a false dichotomy at all. Ignoring that the size of the team is exactly two (and there's only so much you can keep in your head), when you have a metric, you optimize for it. It's not even a conscious process.


I have to update the screenshots for my iOS app, but it seems that I should submit a new binary, which in turn pushes it all my users.

I wonder if it would help to have a checkbox "This update is not worth pushing to already installed devices."


At some point it was possible to update screenshots without uploading a new app. Apple changed it because it was abused by apps that uploaded a crappy app and changed the screenshots after review.

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.


Delta updates are for users, not developers. It means that they don't have to download the full app when updating.


Well yes. But in this case that is the problem no? As a developer I have to upload new binary when changing screenshots, thus forcing my users to redownload a new version. A diff of an identical binary would be zero (or something very small as at least the build number would have to be updated). For developers it would be better too but Apple has always privileged user comfort over developers.


The solution to that is to go through app review, but without submitting a new binary. Let them review the screenshots. In addition to saving users data, this will also ease Apple's work.


For some non-obvious on-point tips regarding binary size and (also importantly) startup speed in complex apps, see ‘Swift with a hundred engineers’ (2016) where Tuomas Artman shared what Uber engineering’s learned from their iOS rewrite[0].

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

[1] https://github.com/robb/Cartography


> Is it actually feasible to use AutoLayout in a larger-scale app without something like Cartography?

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).


Layout Anchors in iOS 9 cut down the boilerplate required to initialize constraints programatically. The tricky part is remembering to turn off translatesAutoresizingMaskIntoConstraints.

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.


I have built many large scale apps and have never used a library for autolayout, it's just not necessary. Using visual format language helps cuts down the code a lot.


This is a quite opinionated piece, and of course not for everyone. I want 100% of all crashes reported, so I need Fabric or similar. And I write way better and more stable code with an RX framework, so I'll use RxSwift or ReactiveSwift/Cocoa. And since I do have to interface with JSON, I will include ObjectMapper or similar (until I can have iOS11 as min target). And sure, I could go by with just NSURLSession, but Alamofire's footprint is not that big, so I'll add that too. Why should my app be smaller than 20-30MB if Facebook can get away with 400MB? There will be 1% of users who are delighted by a small binary footprint, but the rest will not even notice.

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.


Why should my app be smaller than 20-30MB if Facebook can get away with 400MB?

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


> Turbo Pascal was a full IDE and was 2.29MB for all the bits. They didn't use an existing GUI framework either.

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.


Sure. I'm advocating that apps a magnitude smaller are ok-ish - 20-30MB is what I get when I go really crazy with the dependencies.


> There will be 1% of users who are delighted by a small binary footprint, but the rest will not even notice.

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...)


>which they haven't used lately

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.


Most of the third-party apps on my phone are either

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.


> Why should my app be smaller than 20-30MB if Facebook can get away with 400MB?

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%.


Why should my app be smaller than 20-30MB if Facebook can get away with 400MB?

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.


I believe the new Swift compiler announced at WWDC does dead code removal, so including a large library for not a lot of actual use should affect the binary size much less now.


I'm not familiar with Swift, but I'd be weary of treating dead-code removal as an excuse for piling on libraries. Deciding whether code is actually dead looks like a halting-problem-level type of problem. For example, if I write the following Golang code:

  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).


You can already do it with Swift 3 as well, provided you enable LTO, with the side effect of slower build times.


> Why should my app be smaller than 20-30MB if Facebook can get away with 400MB?

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...


One reason to have a small app is to make it download quicker on a slower connection, which means I'm less likely to start randomly downloading competitors while it's downloading.

The quicker you can get me from starting the download to launching the app the less time I've got to find another app.


As the article points out its mostly a review of https://developer.apple.com/library/content/qa/qa1795/_index...


Still relevant, as it appears many developers don't pay attention to what happens at WWDC, IO or BUILD sessions, where these type of advices are repeated over and over.


Does iOS not have incremental app updates? One time big download on Wifi followed by smaller incremental updates is not a big problem when devices are starting to have doubled storage capacities and fast transfer speeds.


Yeah it kinda sucks that Apple makes you re-download the app essentially. Its worse for iOS updates though. A full GB just for a point release?! I haven't use a Mac in a few years, so I'm not sure if its the same on the OSX App Store. Few years back I was aghast at having to re-download XCode for updates, plus it was super buggy then. Maybe its changed now.


Over-the-air updates have been very small since iOS 9. Last one was about 32MB or so.


Hmm, I use itunes to update. Not sure why I'm forced to download such giant files if they already have tech for differential updates.


Yes they do, but that's for content only. The binary size cannot change.


> "Unfortunately, tech CEOs [...] don’t live in areas with shoddy speeds"

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.


On my Arch Linux box, package lists are about 5.8 MiB total. (And yes, those contain basically a list of package metadata and signatures in the repo.) I'm going to assume that Fedora carries more packages (do you have rpmfusion enabled?), so at least the magnitude looks okay.

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.


There are several very clever ways around this, with continual rolling updates in a general fashion with only static files.

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"


That particular distribution doesn't. Gentoo rsync's its main package data, for example.


The points regarding AB tests and analytics simply isn't applicable to most commercial apps.

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.


Facebook's app is so bloated because of one thing: Apache Thrift. Last I checked it came with over 16,000 stubs and serialisers for thousands of different data types.


Everyone should throw their whole source trees at ImageOptim:

https://imageoptim.com/mac


What a luxury to have an app that is only 15kloc. That is why it's small.

If he had a 20kloc objective-c app, how much smaller would of it been ;)


Wow, I thought I was going to read an article about optimizing your code for size, finding ways via assembly to beat your compiler, etc. What's actually in the article are basic "best practice" things that every developer should be doing anyway. I'm glad the article exists, but as a mobile developer, if this stuff is news to you, you might need a wake-up call.


It's a best practice to dump cocoapods?


I've never measured whether using cocoapods to import dependencies produces larger or smaller binaries than not using it. I would imagine it makes no difference, but it's something worth measuring if you are considering using cocoapods and are worried about binary size.

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 do not think so


I am building an app, right now it's about 600MB exploded. My backup, with reduced image quality (winzip for mac does this), drops to 30MB (git is good but 3 backups per day on a cloud is better).

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!


The vast majority of people don't care about the app size unless it's blown out of proportion.

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 disagree. My phone is completely full, every new app I install requires going through and deciding which apps I need to delete to make space for it. Large apps get chopped pretty much straight away unless they are exceptional.


If the vast majority did care, there would not be a 419 MB Facebook app.

I personally analyzed a number of apps and found image assets can be reduced by 30-50%. Not only caring, working on the solution.


People do care, but there is a big difference between Facebook and unknown developer X.


Then why do I get all these ignorance and downvotes by taking action?


People do care, there are sessions at Google IO, WWDC, BUILD where they try to make developers aware that size is one metric by which many people uninstall apps.


I hope Apple charges for bandwidth at ridiculously inflated AWS prices. Ignore the first 100MB of an app and offer a devs a $2500/month budget.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: