Intro

In large scale projects, and arguably in any project, the capability for creating, running and testing parts in isolation is a necessity. A change in one part should not require to recompile the entire app until it is really necessary.

In this article, I’m going to explain how we can create a modular architecture using Cocoapods and Xcode. Something that looks like this:

Motivation

Imagine we need to make a small change in a screen and try it out to see if that change works. If the app we are working on is built monolithically as a single product, we will need to recompile the entire codebase to see the results. This process is time-consuming and tedious. We should be able to compile and run parts independently, in isolation.

Sometimes a part of an application is complex and large enough to be treated as a product on its own. We can call that a module. Modularity is key towards achieving software that can be scaled and maintained over time.

Even though it might add complexity at the beginning of a project, in the long run, we may find many benefits in a modular solution: less build-time when recompiling after a change is made, clearer development areas/responsibilities, isolation of changes, being able to use playgrounds to build and test UI, etc.

External Dependencies

Nowadays, most iOS applications use either Cocoapods or Carthage as an external dependency manager. This article focuses on Cocoapods, but the approach could work with Carthage too.

As far as I know, the most important difference is that Carthage might yield lower compile times in clean builds. Carthage compiles the frameworks outside the build phases of the app, only when we run an update or an install.

I haven’t tried Xcode 11, iOS13 and the Swift Package Manager yet. It seems that structuring a modular architecture with such tools is quite easy. That might be the case, but it’s going to be a while before all apps can target iOS 13 to have those capabilities. So, Cocoapods and Carthage will still be here for a while.

So, how can we achieve modularity using Cocoapods as dependency manager?

Architecture

The approach consists in having a single workspace that contains multiple projects. One project for the final product (the app) and one project for each module. The app depends on the modules and the modules can depend on each other. Finally, Cocoapods provides external dependencies.

Architecture

Each module compiles as a framework that is used by the application target. Modules can depend on each other (no circular dependencies though) and on 3rd party SDKs, handled by Cocoapods.

We define the external dependencies in a single Podfile. When we install the pods, the workspace gets populated with a Pods project. The Pods will output frameworks that each module, and the app, will depend upon.

In the following section, I will explain how to create such a structure.

Setting up the workspace.

Create a workspace. Create a project for the app. Initialize Cocoapods and name the app dependencies. Place the Podfile at the Root level of the workspace, where the workspace file is. (Optional) I like having the Podfile and the Podfile.lock inside the workspace. (Optional) I like having a Playground for each module to try things out. Make sure the app compiles and uses the external dependencies.

The workspace should look something like this:

Initial workspace and folder structure.

Creating a module.

Create a project and place it inside the workspace. Add the module’s dependencies to the Podfile. Add the dependencies of step 2 into the pods of the app too. Run pod install and compile the module to make sure it can use the external dependencies. Add the module as a dependency of the project (follow the steps in Adding an Module as a Dependency of the App).

At this time, your Workspace, Podfile, and Folder structure should look like this: