Getting Started with iOS App Modularization — An Introduction

Arya Surya
Tokopedia Engineering
11 min readSep 16, 2022

--

This is the first part of the modularization tutorial. Refer to the list below for the complete parts :
Part 1: Introduction
Part 2: Extracting Catalog
Part 3: Final Extraction and Micro App

App modularization is an issue everyone runs into as a software engineer, moreover, if your app is already grown to the scale that it takes too long to build the entire app, just like the Tokopedia app. In our case, we have to find a way to fasten our build time to meet our weekly release and this is where the modularization helps. By modularizing our app, we can choose to build what we need at the moment thus improving our build time and everyone is happy!
Tokopedia uses a custom modularization approach to enable fast development, but before going to custom modularize your app, you have to understand the basics of modularization and all the other related things you need to know, this article will cover all of them and guide you to have your first taste of modularization, hang tight!

There is a slew of solutions you can use to tackle modularization. From splitting codebase chunks to increase maintainability to creating good encapsulation. But what is the motivation behind modularization?

When should you split your codebase into modularized parts?

Why should you even do such a thing?

What is Modularization

Modularization is the practice of dividing features of an application into separated modules so that they are interchangeable, reusable, and easily maintained. If you prefer a less-technical explanation, think of a car stripped off its steer, dashboard, and wheels. All these smaller parts of the vehicle can be fully customized on their own and easily replaced by other parts of the same kind if needed.

Most teams fall back to the modularization philosophy to split monolith codebases that are too large to handle. It is because as time goes on, the build time grows linearly with the lines of code. Slow build time hurts developers’ productivity in the long run and will result in long waiting times before developers can fully test their apps.

By splitting the features, developers will only build the needed features without having to compile the entire app. Theoretically, this will decrease the compile time, improving developers' productivity.

When Should You Modularize Your App?

One question might arise when considering modularization. How do you know if your app needs to be modularized or not?

Photo by bruce mars on Unsplash

The answer to such a question is pretty subjective since it depends on your aims from the start and your current situation. For example, suppose you are building a startup that needs to have as many features delivered as possible within a tight schedule. In that case, modularizing your app from the very beginning will result in either :

1. Premature optimization.
2. A good foundation while the app is still in its infancy.

All of these concerns are valid and you must make decisions with this in mind.

How to Modularize An iOS App

Let’s say your team finally decide it is the best time to perform modularization, and the next question is how to start modularizing your app.
Rest assured, you will learn how you can modularize an iOS project step-by-step.

Consider an illustration below :

Let’s say you have an iOS app with three features: catalog, product detail, and shared. One way to get the app modularized is to split the app into four different modules, each with a library and a bundle. In iOS, a module is called a framework, and this article will use those two words interchangeably.

A library is a collection of resources and the code itself, compiled for one or more architectures, in shorter words, it is all the code you have written. Whereas a bundle in iOS is a directory with subdirectories usually used to store assets, files, and many other specific types of content. The framework itself is a group that contains all the libraries and the resources it needed.

Furthermore, there are two important frameworks that need to be explained :

1. Main

Like its name, the main is your main framework; think of it as your app’s entry point.

2. Shared

Think of this framework as the place of all components used within the entire app, containing everything like the code of UI components, (e.g accordions, bottom sheet, popup, etc. ). The shared framework is the best place to start splitting the app since it does not depend on anything externally.

Moreover, iOS has two types of frameworks, dynamic and static, respectively.
A dynamic framework is a bundle of code loaded into an executable at runtime instead of compile time. It behaves exactly like a dynamic library, except for the difference that a dynamic framework is a dynamic library embedded in a bundle along with optional assets, such as images. UIKit and the Foundation frameworks belong to this category.

On the other hand, a static framework is a bundle containing a static library file. They are statically linked, or copied into the executable binary, and not loaded at runtime.

For the sake of simplicity, this article will only cover extracting a monolith into dynamic frameworks.

You can read more about dynamic and static libraries here.

Let’s Get Started

Clone the starter project here

Open the MyAwesomeApp.xcodeproj. Try to run the project to get a clue about the app you are trying to modularize.

As you can see, the app is pretty straightforward, you have a catalog page showing all the product cards and several rows of promotional and inspirational products. Another feature to add is navigating to the respective product detail page upon tapping one of the product cards.

1st Step: Convert App Into Xcworkspace

The first thing you have to do to modularize our app is to convert our app to an xcworkspace

Click file > new > workspace, and set the workspace name as MyAwesome. Then, save the .xcworkspace inside the modular folder.

Another window will appear and you can close the previous window. Click File, Add Files to MyAwesomeApp, and add your previous myawesome.xcodeproj to your workspace.

Your Xcode will then become something like this.

Until now, you have successfully added the xcodeproj inside an xcworkspace. Now, you can start to split the features into their own corresponding frameworks.

Remember that you must start extracting from the least dependable frameworks. By referring to the dependency diagram before, you can see that the least dependable framework is the Shared framework. Start by extracting this framework first.

2nd Step: Creating the Shared Framework

Create a new framework named Shared and add it to our workspace. Refer to the GIF below for the step-by-step.

One thing to note here, remember to choose MyAwesomeApp.xcworkspace the place to add the new framework. On top of that, also make sure the group refers to the MyAwesomeApp.xcworkspace.

Now your workspace will look something like this, with two xcodeproj inside an xcworkspace.

The next step is you have to move the all contents of the folder Shared in the MyAwesomeApp framework ( we will call this the main framework in future usages for simpler and better understanding ) to its own Shared framework’s folder. You can use the help of the finder app in order to do so.
Right-click on the Shared folder in the main framework, choose Show in Finder , we will call this Shared folder the source folder.
Do the same thing for the Shared folder in the Shared framework, and we will call this folder the destination folder.

Move all the folders inside the source folder to the destination folder, and remember to tick copy items if needed when prompted. You can refer to the GIF below for a better understanding.

Next, you have to adjust the module property of the moved asset files. Refer to the UI folder in the Shared framework. Click the custom class section on the right pane of the code editor, click the module text field and type Shared, the framework’s name. Doing so will cause the Inherit module from target checkbox will be automatically unchecked.
This step is to set the resource file’s module explicitly to the Shared framework since you have moved the file there, if you do not, the file will still think it belongs to the main framework, since it inherits from the target, causing unexpected behaviours.

Do the same for the InspirationItemCollectionViewCell.xib and InspirationCollectionViewCell.xib .

The next step is the most essential part of integrating your Shared framework into the main framework.

Navigate to the main framework, locate the Frameworks, Libraries, and Embedded Content section, and choose the Shared framework to be embedded. This step is to tell the main framework that it has the Shared framework as the dependency when running the app.

Try to run the app, but expect the build to fail because of these errors.

3rd Step: Resolving the errors

Let’s solve the errors sequentially, navigate to the CatalogPageNetworkProvider.swift

As you can see, most of the errors are caused by the file not having the definition of NetworkResult , this is because the NetworkResult is originally located in the Shared folder, and because you have moved all of the Shared’s contents to its own frameworks, the code from the main framework does not have access to them again.

The solution for this is you have just to import the Shared framework in the file.

But the error persists even if you have imported the framework, this is still happening because apparently the NetworkResult has an internal access control level, any definition that has internal access control level can only be accessed from its own module, and since you are trying to access the enum from outside the framework, you still cannot locate the definition, which explains the error.

Note that access control level plays a huge role in modularization, you have to determine which code that are exposed and which are not

Change the access control level to looser access controls, such as public or open. Navigate to the shared framework, find the NetworkResult.swift file and add an access control before the enum definition.

The errors in CatalogPageNetworkProvider.swift should be solved now.

Navigate to the ProductDetailPageViewController.swift . The errors are because the file cannot find the definition of the Product struct and the load function since those definitions are already moved into the shared framework, hence the solution is the same as the previous errors, which is
1. Adjust access control levels
2. Import Shared

Import the shared framework at the top of ProductDetailPageViewController and add a public control level in front of the load function definition in UIImageView+Extension.swift . You should have resolved the errors.

Next up, navigate to the CatalogPageViewModel.swift , the same errors are happening in this file, it cannot identify the HashDiffable protocol and the ProductResult .
You can use a similar solution, import the shared framework on the file and add a public access control to the HashDiffable definition and its isEqual function.

Lastly, the CatalogPageViewController , where it cannot identify the Product struct. Simply importing the shared framework in this file will solve the error.

Try building the app and it should have been able to run now.

4th Step: Handling Bundle Issues

One thing that you will notice upon the app launch is your app does not look like it used to be before modularization, you have only presented a white screen and your list of product cards is gone. Why is this happening?

GIF by https://giphy.com/joebuddennetwork at Giphy

Luckily, looking at the app console, you should see the error’s root. A message : URL NOT FOUND.

If you try to find the error string in the entire app, you will notice that it is only coded in the CatalogPageNetworkProvider.swift . Furthermore, If you look further into the code, the errors are because you can't locate the ProductData.json

This happens because the ProductData.json is already moved to the Shared framework, but how can you access the file using a bundle? 🤔

First, to access a bundle from a framework, you have to know its identifier.

Navigate to the Shared framework, click on the target and inspect the general tab. Turns out you already have a bundle identifier of the shared framework, and you can use this in order to access the files within the framework.

Navigate back to the CatalogPageNetworkProvider.swift , change the guard validation in both of the fetchProductand fetchInspiration

Instead of using Bundle.main , use your shared framework’s bundle by initializing it using the bundle ID. Remember to update the bundle ID to your framework’s bundle ID as your organization name most likely will be different.

Try to run again the app. This time, instead of getting the error message URL NOT FOUND, your app will crash.

GIF by https://giphy.com/desusandmero/ on Giphy

Suppose you try to debug further from the stack trace, you will see that the crash is caused by CatalogPageViewController calling the cellForIndexPath function, which triggers the collectionView to load the ProductCollectionViewCell, but it was unable to do so, causing the crash.

On the brighter side, this crash proves you have successfully loaded the JSON files since it is happening on the CatalogPageViewController ‘s function that is triggered upon receiving data.

Your view controller causes the error since it cannot identify the ProductCollectionViewCell . The solution is to specify the bundle where this view controller loads the NIB.

Locate the viewDidLoad function inside the CatalogPageViewController , and change the code to become :

Try running the app, and you will get another crash caused by the same issue, the InspirationCollectionViewCell could not load its NIB. Locate its awakeFromNIB function and change it into this

Remember to adjust the identifier to your own bundle ID

After changing the code, try to rebuild the app and it should run successfully!

You have successfully extracted your shared module, leaving only two more features before you finish modularizing your app. We will continue extracting the catalog feature into its framework next.

You can access the code materials until this point here.

Now let’s continue to the second part here

--

--

Arya Surya
Tokopedia Engineering

Mostly writes about frontend, including but not limited to iOS and web development. Go follow @agustinustheoo for other stuff