Modular Apps with a Tuist — Part 2 — Network Module

Ronan O Ciosoig
5 min readMay 11, 2021

--

A step-by-step code walkthrough converting an existing app to a modularised app using tuist.

In the previous article I started the process of modularisation by making the Xcode project and workspace be generated by the tuist tool, and refactored the dependencies replacing CocoaPods with SPM or creating a new project. In this article I will go through refactoring out the networking code into a separate module while ensuring that all the automated tests continue working.

Note that this article will only really make sense if you follow each step along the way by looking at the commits in this repo. Clone the project and use SourceTree or similar on the PartTwo_NetworkingRefactor branch. Most of the commits are linked in this text.

A Bit of Style

Before I take a dive into breaking out the network code, there is just one thing I overlooked last time — SwiftLint. This was defined in a build phase in the original project and I forgot all about it.

As it happens tuist supports a lint command, with 2 main options “code | project”. The code parameter will lint all targets, but a target can be specified like so:

tuist lint code Pokedex

Running the project lint is fine without any issues. Linting the source code however, not so much. The original project uses a Swiftlint YML file and this is in the root of the repo. The tuist lint command doesn’t have an option to specify the configuration file but it does pick it up if it is located in the root of the tuist project. Without the configuration file the lint gives 253 violations, 2 serious. Copying over the YML file and fortunately it is a much more reasonable 0.

But of course the core idea is that the linting is run every time you run a build — it needs to be a build phase like before. I add a few lines of bash to scripts/swiftlint.sh, give it execution permission, and then (tuist edit) update the makeAppTargets with:

actions: [                
TargetAction.post(path: "../scripts/swiftlint.sh",
name: "SwiftLint")
]

Opening the project again and selecting the build phases now shows SwiftLint. Job done.

Xcode build phase showing the syntax for calling swiftlint

Back to the refactor.

Splitting Layers

In the terminal, cd to the repo root and mkdir Network, then ‘cd’ to that and run ‘tuist init’ to create the Network project.

An aside here is that there is a different approach where frameworks can be added to the main tuist project but that involves a bit more preliminary work configuring scaffold templates, and I will go through this process in a future article.

Next, I move the Services folder to the NetworkKit sources folder. The Network app target is there to validate the NetworkKit framework and will consist of a simple view controller and connects to the network kit code to make the API call. The NetworkUI target is not needed, and can be deleted. The project definition needs to be updated to remove this dependency, and then extended support a packages parameter in the same way that Pokedex was updated, as well as several changes done to the project template. The packages Moya and Result are added to the framework target. When I run the compile I see that both Configuration and Constants are required so these 2 are copied over. After that is done, then there is just one small fix required for Swift 5.3 — update keyword for the protocol as ‘class’ is deprecated.

At this point the NetworkKit framework builds, so I move over the existing test case “testEndpointReturnsData”. When code is in a framework and needs to be accessed outside that framework then it is possible to remove the @testable in the import statement for the tests since it should be defined as public anyway.

The next task is to use it in the Network example App. Here I add a SimpleViewController that contains a label, textfield and button whose action method triggers the search, which is implemented in the DataProvider class. The DataProvider instance imports the NetworkKit and makes the call to the API, with the response triggering a call on the notifier instance, which is a protocol implemented by the view controller, and completes the cycle. I copied over the image assets and updated the launch screen for completeness.

Rewiring The Machine

With the Network module now complete, the main Pokedex app needs some attention to make it compile once again. By changing the project source and adding target dependencies to the LocalFramework definition, this solves the dependency management problem with NetworkKit using Moya and Result. Note that the main Pokedex project still has to define the packages for Moya and Result even though they are also defined in the Network module. I was hoping to fully encapsulate all the dependencies within the framework but it doesn’t work in the current version of tuist.

Now that the coding changes are done tuist has a nice tool to visualise how it all comes together: graph.

The Network module looks like this:

And then the Pokedex module looks like this:

That completes the tasks for this article. In the next article I will go through the process of separating out the UI layers.

If you find this useful, please follow to get the next updates.

Recap

I covered how to define a custom script that launches SwiftLint in the build phase, then adding a new project, loading a framework in a sample app, validating unit tests and re-integrating this into the main app, thus keeping the same functionality as before.

In the next article I go through the steps of breaking out the UI into a module, and go through some gotchas and problems that arise.

The completed code for this article is here.

--

--

Ronan O Ciosoig

iOS software architect eDreams ODIGEO and electronic music fan