Solving modularised iOS project challenges by adopting project generation

Daniel Jankowski
VeepeeTech
Published in
5 min readJan 7, 2021

The teams at Veepee focus on one feature area, mostly related to a particular part of a business: user engagement, sales, travel, etc. In a single team there are people from different disciplines. It makes a team cross-functional and autonomous.

The described structure has been embraced in iOS project structure. The project has been gradually migrated to modularised setup and contains many modules divided into the main categories:

  • product modules — dedicated frameworks per feature area, e.g. sales
  • demo applications — dedicated frameworks per product module, it makes a daily development very effective: short compilation times, sharp focus on a feature and better separation of concerns
  • core / utility modules — networking, deep linking, logging, etc.

The modularised iOS setup has been working very well for us so far, however a few challenges came along the way.

Modularised setup challenges

Inconsistencies in project settings and structure

There’s a lot of things to keep in sync between Xcode projects, like settings, compiler flags, Swift version, to name a few. Xcode Build Configuration Files can address build settings level, however it’s just a small part of the things that can to configured. Let’s not forget about build phases, schemes, and so on.

Structure of the project (source code and resources location, a way of providing mocks, demo applications) is another thing that is hard to keep in sync between Xcode projects and yet very helpful to have. It saves developer’s time by providing a very familiar development experience when jumping between different modules.

Dependencies management

To manage project’s dependencies, we are mainly using Carthage:

Carthage builds your dependencies and provides you with binary frameworks, but you retain full control over your project structure and setup.

The developers must do all the dependencies linking manually by their own. Maintaining the dependency graph is very hard to do right, especially when the number of dependencies are growing.

The effort to create a new module

In order to create a new module, the developers were given a set of screenshots to follow. The whole process was time consuming, manual and error-prone.

Git conflicts on project generated files by IDE

A change to an Xcode project, e.g. adding a file or changing a setting, is reflected in a file called project.pbxproj. This file is auto-generated by Xcode and it is not easy to read by human. The changes in project.pbxproj file happen quite often, and thus a developer needs to face resolving difficult git conflicts frequently.

Project generation for the rescue

After doing some research, we concluded that having a better way of generating projects would solve described modularised setup challenges.

We have decided to give Tuist a go.

Tuist is an open source command line tool, that generates Xcode projects based on manifest file called `Project.swift`. The manifest is written in Swift and it is the equivalent of `Package.swift` in Swift Package Manager.

There are a few reasons why we have chosen Tuist over other Xcode project generators:

  • the project is written in Swift, which is the language that iOS developers are very familiar with. It makes easier to understand the source code of a tool and contribute to the open source
  • auto-completion while working on manifest files
  • compiler errors if anything’s broken in project manifest files
  • the open source community is very active and helpful
  • reusability of project definitions
  • appreciable command line interface and ease of use

Generating Xcode project with Tuist is as simple as calling a single command at the location where a project manifest file is stored:

Generated project is optimised: it contains the bare minimum to work with, which means faster compilation times and indexing.

Adopting Tuist was a bit time-consuming. We started from the modules that didn’t have any dependencies and moved to the ones with more complex graph. The most challenging part was to migrate a main application target. Along the way, we discovered a lot of files that were not a part of any module and redundant dependencies linking.

There is a dedicated section in Tuist documentation that provides guidelines to make the adaptation process as painlessly as possible.

Tuist has an option to define a reusable pieces of the project via project description helpers. It allows to have an opinionated and consistent project structure and it makes the maintaining the ease. We leverage project description helpers intensively:

An example of defining a network framework with a help of project description helpers.
A corresponding project structure on the disk.

Due to manifest files, managing dependencies became very straightforward:

An example of defining dependencies of Sales module with a help of project description helpers.

A wide range of dependency types like a project, a framework or a Swift package is supported. The explicit definition of dependencies in manifest file helps with maintenance very much. To get even closer look at the dependency graph, a developer can execute tuist graph command to visualise it. On top of that, Tuist validates the graph and the project generation will simply fail if something’s wrong.

In order to create a new module, a developer creates new manifest file and uses project description helpers to define the module. The only restriction is to keep a correct directory structure for source code, unit tests and resources. The process of adding new modules is effortless comparing to what it was in the past, but there’s still a few manual actions required. In the future, we are going to automate the process completely by using scaffolding feature.

Last, but definitely not least, git ignoring pbxproj and xcworkspaces files has eliminated the problem of solving difficult conflicts on project files. This change affected the local development workflow a bit. When a developer changes a branch and the project was modified, e.g. new files were added, the project should be re-generated via tuist generate command. To get rid of manual actions when switching branches, a dedicated git hook will be introduced in the future.

Conclusion

Project generation has helped us to solve the challenges we had faced with a modularised iOS project setup. The journey wasn’t easy, but definitely paid off. Looking into the future, we are going to continue to modularise iOS project with speed and confidence.

Project generation is just a tip of the ice-berg though. Tuist is constantly evolving and provide a bunch of features that we’d like to adopt into our pipelines, e.g. caching dependencies.

Tuist has been playing a crucial role in our project generation adaptation. Special thanks to everyone involved in this amazing open source project ❤️

--

--

Daniel Jankowski
VeepeeTech

Hi 👋 I love open source community 💚 Core Contributor of /fastlane/ 🚀