🔀 How to untangle and manage build distribution — Webinar, May 16th — Register
🔀 How to untangle and manage build distribution — Webinar, May 16th — Register

Leaving iOS or: How I Learned to Stop Worrying and Love the Web

Working at Runway is weird.

You have a company of people that are institutionally caremad about how mobile apps are released, and who try to make it better and easier for everyone. 

And yet, we build Runway using <code>React<code>/<code>Redux<code>/<code>Redux-Sagas<code>/<code>TypeScript<code>/<code>antd<code> and <code>go<code> for the backend. Eagle-eyed readers might have noticed that nowhere in that list is <code>Objective-C<code>, <code>Java<code>, <code>Swift<code>, or <code>Kotlin<code>, i.e. the stuff people who know the despair of trying to wrangle app releases are mostly familiar with. 

That stack is also not what I knew prior to starting at Runway; before this, I had been building iOS apps for ~10 years. Don’t get me wrong, that’s not to say I was exclusively writing Swift or Objective-C during this time — a lot of tooling around dependency management and release automation (<code>CocoaPods<code> and <code>fastlane<code>) are written using Ruby, and you inevitably end up writing some glue shell scripts here and there. 

And I used to write a fair amount of these, having been caremad about the mobile app release process for a while before joining Runway, and thus naturally falling into “the CI guy” role at a number of jobs over the years. I always enjoyed working on these; there’s certain satisfaction in making the release process as automatic and streamlined as possible. Evolving a process from a long, involved slog that required a specific person to be online, to one where you can comfortably kick off new builds from your phone can feel downright ecstatic! 

I think good release processes and automations are like well-working tools — they’re not overly exciting, and they’re not flashy… but executed well can be great team multipliers. They help to reduce the mental workload and overhead for the entire team and let you focus on building the thing you’re excited about, instead of spending valuable time and mental cycles on other things.

So you can imagine how magical it felt to hear about Runway for the first time. A whole company and a product that’s basically dedicated to making that part of the job as painless as possible, and doing things that are borderline impossible for a small mobile infrastructure team to accomplish? Someone will take over maintaining my hand-rolled and glued together <code>bash<code> and <code>fastlane<code> scripts, and let me see all the information (all PRs going into a release! All tickets fixed!) about a given release in one place? And it has stuff for release monitoring too? Hell yeah, sign me up!

And then, in a semi-ironic twist of fate, I joined the company and indirectly became responsible for maintaining Runway instead. You can never escape caring about releases, it seems like. 

The transition

But the wholesale jump from the mobile world to the web one was still daunting.

Do you enjoy feeling like the biggest idiot on the face of the earth? Do you enjoy struggling with aligning two elements with each other, whereas you could do that in your sleep in your previous tech stack of choice? Boy, do I have an offer for you!

In all seriousness, making the jump is not easy. There’s always a learning curve when learning a new language, new framework, etc — but changing domains of what you work on — having to think about an entirely new set of constraints and opportunities that a browser has, as opposed to a phone — is a whole new level of difficulty and feeling overwhelmed. At least Swift UI and Jetpack Compose make React feel a bit more familiar. And simple backend jobs like plucking some things from a database and displaying them as JSON don’t seem to change nearly as rapidly either. 

Maybe not unsurprisingly, the closest analogy I could find to compare the two worlds is the philosophical difference between macOS and Linux.

On one hand, you get a system that forces you to use it a certain way, but mostly just works — as long as you stay within the designed parameters. On the other hand, you get a system that will work exactly the way you want it to, but you have to be willing to put in elbow grease to get that.

The good

Do you find using One Blessed IDE and having to wait for a vendor to fix their bugs in it annoying? Do you wish you could just use different, open-source, community-supported tooling?

Great! You'll love the web and the modern tooling around it. You can tinker with anything and everything and there are approximately 20 tools that do very similar things. If you don't like <code>webpack<code>, there's <code>gulp<code>, <code>rollup<code>, <code>parcel<code>, <code>esbuild<code> and probably seven others I'm afraid to even learn the names of. I won't even bore you with the usual "lol there's so many JavaScript frameworks!" joke, because, come on, it's 2022, but... it didn't come out of nowhere? The ecosystem is enormous and you're bound to find an opinionated framework whose opinions you agree with.

This counts double if you also enjoy tinkering with your editor and build tools. With a few (dozens) of extensions, you can turn VS Code into an omni-language IDE that can have bells & whistles ranging from "vim keybindings" to "a cute small animated pet". You have 10 years of muscle memory of smashing <code>command+r<code> to launch your app? There's an extension that brings keymap from your previous IDE! I could go on like this all day.

One of the first things I contributed at Runway was a VS Code configuration that lets us:

  •  build the <code>go<code> backend
  •  build the frontend app
  •  start both of those
  •  attach a respective debugger to each of those
  •  open a browser window with the app running in it

…with a single keystroke! It's... genuinely amazing that it's even a thing you can do! I can put breakpoints in both the <code>go<code> and <code>React<code> code, and it just works! Inside the same IDE! It even supports multiple browsers! I genuinely love it.

Similarly, for someone who doesn't have a whole lot of React and modern CSS/HTML experience, being able to quickly try stuff out with near-instant hot code reload is a godsend. It's a fantastic experience to just be able to change a line of code and see the result immediately, and see whether you managed to align the two rectangles on the screen you were trying to. No long compilation cycles! No need to navigate to the screen in the app you were trying to fix!

Just you and your CSS failures.

One thing that I now dearly miss when working with any other language is how common and prevalent tools like <code>gofmt<code> and <code>prettier<code> are. Even skipping over the benefits of never having to have a single discussion about formatting in PR comments ever again, having these happen as you type is amazingly freeing. I never worry about aligning anything — I just keep typing and the code gets re-shaped with correct whitespace automagically.

<code>eslint<code> and <code>go vet<code> and similar tools are also great. More of computers yelling at me for making mistakes they can detect, please!

Then there's all the benefits of using open-source tools that have release cadences that are (at least somewhat) divorced from release cycles of A Major Corporation. You get bug fixes faster, and you get to see discussions about a bug that's been annoying you for the past four months. Having more transparency into the development process is just really nice — it just feels good when you search for an issue, and see a bunch of smart people discussing it already.

The bad

Do you find using One Blessed IDE and having to wait for a vendor to fix their bugs in it annoying? Do you wish you could just use different, open-source, community supported tooling?

No, you don't. Or at least I don't. I mean, I do, it's annoying. But it's also a blessing. I didn't have to think about it too much. I just opened Xcode, adjusted like three configuration options and was ready to go. The choice paralysis with the web is real, and it's not fun.

When you want to add a shiny new tool to your iOS project, you have maybe three options to do it. You may not love any of them, but the total number is small enough that when writing any sort of guide, or creating a new project, you can just be exhaustive and cover all the bases. Because of the plurality of the tooling on the web, that's not really feasible. You're on your own for a lot more.

You know the cool hot reloading experience I mentioned above? Setting it up was a nightmare. To be fair, the last time I did any infrastructure work on a web app was in 2012, when Django and jQuery were the cool kids on the block. But in 2022, most of the docs and guides out there either cover just some of the near-infinite matrix of possible configurations, or they just assume you know what you're doing and don't need much hand-holding. Which, unfortunately, I ended up needing.

Having to find out that it is now considered Normal and Fine to have a tool like <code>webpack<code> be used as a development web server was quite the culture shock. Setting up proxying requests from <code>webpack<code> to our app server, and not the other way around still seems wrong to me, but such is life in Single Page Application world. Looking forward to using <code>webpack<code> in a few years to check my mail! Getting real <code>systemd<code> vibes over here!

Thankfully, TypeScript has mostly removed any reason for me to complain about how confusing and disorienting it is to jump back into the world of languages without static typing. Stepping outside of the warm and fuzzy typed comfort zone and into the broader world of JavaScript still can feel confusing and maddening though. A real-world example of this comes from me struggling to configure <code>webpack<code> to use a different WebSocket server path.

The property related to it is documented in <code>webpack-dev-server<code> as accepting <code>false | 'sockjs' | 'ws' string function object<code>. That is... not very helpful? What kind of <code>function<code>? What signature does it need to have? What are the parameters it accepts? What kind of <code>object<code>? Just tell me anything, I beg you.

Turns Out, one way it works is if you pass a configuration object for a <code>ws<code>  node library, which has a <code>path<code> field, it will pass it along! Which I am honestly not sure now how I figured out, because the docs sure didn't tell me!

Oh, and the counterpart option to change the path the client tries to connect to is not called <code>path<code>, it's called <code>webSocketURL<code>, because of course it is.

I’m also sorry, Go, but <code>[Key: Value]<code>, <code>[Type]<code> are the obviously correct syntax choices for declaring a dictionary or an array. What the hell is <code>map[string]int<code>? I’ll give you that <code>[]int<code> at least makes some sense. But don’t even get me started on <code>[]{1, 2, 3}<code> as the syntax for array literals. How? Why? Who hurt you? 

I also get that it’s antithetical to the Go philosophy to have higher order functions like <code>map<code> or <code>filter<code>, but having to pull in a library just to remove an item from an array in an idiomatic way seems like maybe some things got taken just a bit too far.

Is it just me? Am I an old man yelling at the clouds? Am I being unfair to a few projects I encountered and painting a whole community with a broad brush based on one bad experience? Probably all of the above, a little bit.

The ugly

Apart from things that I don't like, there are things that I find downright... confusing as how they're even a thing.

The <code>go<code> debugger (or, more specifically, the <code>delve<code> debugger and its integration within VS Code) is a good example of that. At first I was convinced that being able to call methods dynamically from within the debugging session just wasn't supported, but thankfully Turns Out it just wasn't supported on <code>arm64<code>, and has since been implemented. There are still things that are however still a complete mystery to me. Did you know there are (very low) limits to the length of a string it's willing to print out via the console?

You're a debugger, one of your main jobs is to print out things! Debugging failing JSON serialization, when you can't see the entire string is... not a great experience!

For a language that is supposed to be simple and straightforward, the surprising iterator behavior in Go is also the biggest footgun I’ve ever seen in a mainstream language. That one, thankfully, seems to be getting fixed in a future release though.

On the other side of the fence, I understand there's probably a Good Historical Reason for it, but for TypeScript to have a powerful <code>null<code>/<code>undefined<code> checker and leave it <code>off<code> by default is baffling. I get the need for an escape hatch, but having it be the default, and thus making the language more dangerous for newcomers who aren't experts in it and all the knobs to configure it, is borderline inexcusable.

Similarly, <code>webpack<code> using <code>/ws/<code> as a default path for its own WebSocket related to Hot Module Reload is borderline user-hostile. Prefix the things coming from your toolchain! Especially when you enable them by default! I had a cool afternoon wondering why the WebSockets for our app stopped working for me locally.


As much satisfaction and joy I previously got from making my teams' releases better, knowing that the work I do on Runway will help many more teams ship faster feels even better. I get excited by the prospect of someone like me-from-a-year-ago seeing Runway for the first time and having their mind blown by the prospect of having almost the entire release management problem solved by someone else. 

And while after 10 years I’m no longer directly involved in writing mobile apps, still being closely involved in helping to build them helps in reducing the emotional component of making a career shift like that — especially given we talk to teams using Runway every day. Fundamentally, making a great tool requires you to have a good understanding of the problems of people who will be using it — so we’re closely watching and participating in mobile communities still.

Overall, I do enjoy my newly adopted tech stack. It's a big change from what I'm used to, and I'm still frequently feeling like a total newbie (which I am, to be fair). There's a lot of paper cuts and things that are just different from what I'm used to, but I'm adapting. It's frustrating, but it's also rewarding. But most importantly — I get to work on things that I care about, and ship them to people, and that's what matters. 

Even if I still frequently feel like an idiot trying to accomplish such complicated tasks as “save some items to a database”.

I still just wish the debuggers were nicer though.

Don’t have a CI/CD pipeline for your mobile app yet? Struggling with a flaky one?
Try Runway Quickstart CI/CD to quickly autogenerate an end-to-end workflow for major CI/CD providers.
Try our free tool ->
Sign up for the Flight Deck — our monthly newsletter.
We'll share our perspectives on the mobile landscape, peeks into how other mobile teams and developers get things done, technical guides to optimizing your app for performance, and more. (See a recent issue here)
The App Store Connect API is very powerful, but it can quickly become a time sink.
Runway offers a lot of the functionality you might be looking for — and more — outofthebox and maintenancefree.
Learn more
App Development

Release better with Runway.

Runway integrates with all the tools you’re already using to level-up your release coordination and automation, from kickoff to release to rollout. No more cat-herding, spreadsheets, or steady drip of manual busywork.

Release better with Runway.

Runway integrates with all the tools you’re already using to level-up your release coordination and automation, from kickoff to release to rollout. No more cat-herding, spreadsheets, or steady drip of manual busywork.

Don’t have a CI/CD pipeline for your mobile app yet? Struggling with a flaky one?

Try Runway Quickstart CI/CD to quickly autogenerate an end-to-end workflow for major CI/CD providers.

Looking for a better way to distribute all your different flavors of builds, from one-offs to nightlies to RCs?

Give Build Distro a try! Sign up for Runway and see it in action for yourself.

Release better with Runway.

What if you could get the functionality you're looking for, without needing to use the ASC API at all? Runway offers you this — and more — right out-of-the-box, with no maintenance required.