Wrestling with a Monstrous Xcode Project
By Harshad and Nadine
Writing an app is pretty easy. All you need is an idea, a design, and an API. This is what most apps are. And this is what GO-JEK was once upon a time.
We started simple: with only three products. The features were limited, the codebase was small, and so was our team. Over the years, we added a dozen more products. Our codebase grew to over half a million lines of code and the team, to over 20.
While this is amazing progress, there are also some problems unique to a massive Swift codebase. One of them being — torturously long compile times.
You could grab a snack, go to the restroom, and watch an episode of Friends, while GO-JEK built.
It makes for a funny anecdote now, but in reality, we were suffering. We were wasting half of our day compiling the app. At times we took weeks guessing which state mutation from which thread is causing that annoying weird crash. This was the time when simple text changes took 30 minutes to verify. It was exasperating.
We tried to hack our way through this by removing type inference, buying faster computers, adding cryptic compiler flags we didn’t really understand, or praying for a Xcode update which magically fixed it all. But this would only get us so far. We needed a permanent solution. The answer — experimenting with different architectures.
GO-JEK went through wave after wave of architectural trends. We implemented and re-implemented parts of our codebase using different patterns like MVVM, MVP and even VIPER.
While these popular patterns helped us structure our products internally, we couldn’t apply them to structuring the GO-JEK app as a whole. The solution to our long compile times was simply compiling less code at a given time.
Did we really need to compile all that code?
Say you’re working on GO-RIDE. Why should you need to compile GO-FOOD or GO-SHOP? This is how we started tackling the problem. The aim was to only compile the product the team was currently working on.
The first step to achieving this was separating product code from GO-JEK’s main codebase. This means all GO-RIDE code goes into its own framework, in its own repo. Now repeat this for every GO-JEK product. Compiling each product framework is almost instantaneous, simply because we were compiling 1/20th of the code.
We now have all GO-RIDE screens in one framework. But how do we place a GO-RIDE order without the GO-JEK home screen?
Enter Launchpad
Launchpad is what remained from the GO-JEK project after we removed all product-specific code out of it. It’s a framework that holds the GO-JEK home screen, settings, and booking history screens. We also added common tools such as analytics and location services into this.
Now we have everything we need to place a GO-RIDE booking. All we have to do is combine these two frameworks into a GoRide.app! Its home screen looks almost identical to GO-JEK’s, but with GO-RIDE as the only product.
When you click on a GO-RIDE icon on the GO-JEK home screen, Launchpad needs a way to take you to its booking screen. We do this using a protocol called Launchable, which is exported by Launchpad. Launchable also declares properties for product names, icons, and functions to redirect to various screens. And GO-RIDE, along with all other products, has a single public class in it which conforms to Launchable.
As far as Launchpad is concerned, every product is just a Launchable.
Launchpad accepts an array of Launchables during initialization, and shows their icons in the home screen. When the GoRide.app launches, it creates an instance of Launchpad with just the GO-RIDE Launchable. So, the home screen shows only GO-RIDE in it. Launchpad then takes over the application and sets up the home screen, the tab bar and logs you in. The application target, in this case, GoRide.app, is only responsible for creating instances of Launchpad and GoRide. Everything else is taken care of by Launchpad.
Now, let’s say you’re feeling nostalgic and want to run GO-JEK, but only with its original three products; GO-RIDE, GO-SEND, and GO-MART. All you have to do is add the three Launchables while initializing Launchpad.
The beautiful part is that Launchpad doesn’t have any dependencies. It runs on its own, whether you have zero or 25 products registered to it. Running it with no products gives you the same home page, but with no product icons. While running it with all of our 25 products gives you the familiar GO-JEK home screen we all know and love. ❤️
Before Launchpad, our app was clunky and unmanageable. Testing a product flow was almost impossible without feeling frustrated. Creating Launchpad required us to rewrite the GO-JEK platform, and we ended up eliminating 80% of our code! Compiling a single product app is now almost instantaneous, and we improved the compile time of the overall GO-JEK app too.
We eliminated inter-product dependencies when we sandboxed products into their own frameworks. We also reduced the number of public types in our app. This led to our incremental builds taking only a few seconds. But sometimes, like after a pod install or a long holiday, you have to start fresh and do a clean build. Enabling Swift whole module optimization was the final trick in our bag.
The clean build time dropped from 30 minutes to just 3!
With sandboxing and a normalized Launchable interface for every product, our product teams now have the freedom to choose any design or architectural pattern of their liking. We can now experiment with new programming styles without affecting the whole app.
Launchpad also helped us improve the quality of our application. Since Launchpad now controls the lifecycle of every product, it can create and free resources as needed. This brought our overall memory usage down and reduced the launch time, so our app runs as smooth as butter. Now even an iPhone 4S can run our app like a champ.
We spent two months building, debugging, cursing, hating, and ultimately loving Launchpad. The QA’s spent just as long testing it. But we can now reap the benefits of our hard work and easily build our future projects on this foundation.
If this is the kind of project you would like to work on, we would love to hear from you! Visit gojek.jobs for more :)