Quick architecture review of Signal’s iOS App using SwiftAlyzer

Today, I want to present to you SwiftAlyzer, a tool to review Swift project’s, a tool to help refactoring and keeping it maintainable. As a software architect I find myself oftentimes reviewing projects. Either new projects that are taken over from other companies. Or monitoring the health of an ongoing in-house development. While every feature of a software can be well structured applying design patterns and code heuristics, it’s oftentimes the ensemble of all features to a whole app, that lacks a good structure. It’s where dependencies from one feature reach out to another and back and forth, that lead to an overall maintainance nightmare. Every feature on its own can be quality-checked by looking at the source code, reviewing in so called pull requests to review its quality. It’s hard to get an overview of the inter-class dependencies when looking at source code solely. But often it’s these dependencies that ruin the maintainability slowly but steadily when not supervised.

What makes a good (Swift) software project? That depends on a vast number of variables. In this article I don’t want to start a broad discussion on the „best“ architecture to choose or pinpoint Signal’s weaknesses. My aim is to demonstrate SwiftAlyzer and explain its unique strength as review tool. Therefore I will analyse Signal’s iOS app – but without ever looking at the source code or doing any analysis beforehand.

As mentioned above, I focus on dependencies as a main root of evil. This is explained further below. I try to give more information in these yellow boxes whenever I feel like my arguments could do with some more explanations.

Their’s a lack of tools that support a quick review and an oversight of the app’s structure and its dependencies. That’s why I started SwiftAlyzer. Today I want to demonstrate it’s review capabilities using Signal’s iOS app. If you want to follow along you can get your free copy of SwiftAlyzer on https://swiftalyzer.com and download Signal’s source code here https://github.com/signalapp/Signal-iOS.
(I checked out the develop branch at commit 962369b2676a10da9cf130aa27c87f7c45c325cb)

Analysing the building blocks on 1st level

Displaying the first building block view level zero of Signal iOS app
The first impression are the modules the app is separated into. Swift’s standard libraries (and Cocoapods dependencies) can be shown too, but are hidden by default. These modules are the building blocks on the highest level. We will dive deeper into those later on and see building blocks for every Xcode group down to the classes at the lowest level.
What we see here is the four modules the app consists of and what is immediately noticeable, is the interconnection of each pair of modules. While it’s a good idea to break down big projects into several modules, these should have as few dependencies as possible. Modules should (as far as possible) work on its own, consisting of features that belong together. Features of different modules should (as far as possible) stay independent and therefor should have no connection among each other. The current situation leads to a very high CCD value which is definitely a thing to avoid.

Dependencies are unavoidable, but the number of dependencies should be as low as possible. Since every dependency introduces some complexity. If a class A depends on class B, changes on class B could lead to bugs and incompatibilities in class A. If class B has 10 dependent classes, every change on B could lead to followup changes on those 10 further classes. If furthermore class B depends on a class C, these dependencies sum up and can lead to maintainability nightmares.

The correct term is Cumulative Component Dependency or CCD in short.
It’s defined as follows:
CCD is the sum over all components Ci in a subsystem of the number of components needed in order to test each Ci incrementally. This is true for direct and indirect dependencies. 
SwiftAlyzer calculates this metric for classes, Xcode groups and modules.

The difference between Signal module and Signal Messaging module is not clear, but SignalShareExtension module will probably implement an iOS share extension according to App Extension Programming guide. The NotificationServiceExtension module will be used to implement notification previews, using iOS notification services. So what’s more interesting here is the first mentioned two modules. So let’s dive deeper.

As mentioned before, I didn’t take a look at the source code. But that would not help here too much, since we’re looking at bigger structures than classes. At this level it’s not as easy to guess the purpose of a structuring unit. We can make an educated guess looking at the names and hope that the content follows the semantic proposed by the naming.
At this level I usually resort to architecture documentation like ARC42. But in one of SwiftAlyzer’s upcoming versions, there’ll be a documentation feature. Hovering over or selecting a unit fades in its description – provided as contained readme or so.

Analysing the Signal module and its packages

A deeper look into the „Signal“ module, one of Signal app’s main modules, unfolds a series of packages. There’s a separation of test code from production code and some further packages filled with resources.
A deeper look into the Signal module reveals six packages.

Actually they’re Xcode groups but I like to call it packages which should not be confused with Swift packages. In the Java world packages are the means to structure classes into groups of common functionality. In Swift projects we got Xcode groups to structure classes into bigger units. SwiftAlyzer makes use of classes, Xcode groups and modules to give a higher level overview of the project and its dependencies.

Four resource packages filled with fonts, sounds, Lottie animations and support files. The remaining two are distinct into test and production code. Hiding all resource and test packages and further extending the src package shows the following:
Eight packages inside the src package: Storyboards, User Interface, util, network, Models, Jobs, environment, Calls.
The first thing that comes to mind, is that the Signal messaging module has a bi-directional dependency to almost every package. And a circular dependency to every package due to inter-dependencies among the packages. As mentioned earlier, this breeds a maintenance nightmare and reads as very high CCD value.

The cause of the dependencies between these packages and the Signal Messaging module is left as exercise for the reader 😉 I’ll take a closer look at the inner-package dependencies of the src package further blow.

The second thing that strikes me as odd, is the organisation of content by source type, i.e., user interface, model, Jobs, etc, instead of a feature-based separation. This type of partition does not grow well. When implementing a new feature, it’s models, user interfaces, etc are distributed among several packages, mixed with classes of every other feature. This has two main drawbacks. Firstly, there’s no limit on the growth of a package, which makes it harder to navigate around the Xcode project. Secondly, there’s no (semantic) correlation between the classes of a package, which makes it impossible to plan dependencies on a package or feature level.
A look into the UserInterface > ViewControllers package confirms this issue. There’s a huge amount of ViewController classes, all stuffed together without a semantical connection. With a huge amount of files per package, and a huge amount of dependencies, the project’s complexity explodes and even a package-based visualization gets to its limit. But that is not solely a visualization problem, it’s also an understanding problem. It’s easier to comprehend a feature that consists of 10 files instead of 100. It’s easier to navigate and grasp a project one feature at a time instead of being confronted with all the view controllers at the same time. If a project is separated into distinct features, each as a separate package, it becomes possible to trace and monitor a feature’s dependencies on other parts of the app by tracing it’s package dependencies. If all view controllers of all features are bundled into the same package, it’s impossible to distinguish the good dependencies from the superfluous or wrongly created ones.

Deep dive into a package to discover applied design patterns and infringements

Investigating the inter-dependencies of packages inside the „src“ package. The connections from Model to network package alongside User Interface to network package clearly indicate a lack of communication architecture.
At this level the dependencies disclose a lack of communication plan. Without looking into the source code and investigating the underlying design patterns, a connection from Model to Network indicate a smart, reactive (View) Model. MVVM as a design pattern suggests View layer classes (View Controllers) to use View Models, which in turn take care of data retrieval, typically isolated by Use Case or Interactor classes. Here we see View layer classes (originated from User Interface) using Model classes but also directly classes of the Network module. This mix of communication pipelines further increases the project’s complexity. An application of a common pattern helps to understand and navigate the source code, since one can rely to come across the same structures. Assuming MVVM is the preferred way to go, the illegal dependencies are the ones from the UI layer to the Network layer. To eliminate those lets first hide the other packages. It won’t help much to expand the View Controller package due to its size. Instead the information panel can be used to investigate the incoming and outgoing edges section as seen in the following screenshots:
The incoming edges section lists the ContactViewController and GifPicker classes as directly accessing the Network layer. One improvement can be to move that dependency into the Model layer and eliminate the direct connection to the Network layer. This makes the communication pattern more uniform and thus easier to understand and maintain.

Where to go from here:

If it made you curious feel free to sign up on swiftAlyzer.com and try it out on your own! There are more things that can and should be investigated, e.g. why the util package is not a sink.
Also feel free to reach out if you have any questions or comments. Love to hear feedback from other developers ❤️