Developing iOS applications with Uncle Bob’s Clean Architecture

Jonas Schaude
11 min readOct 3, 2020

In the recent past, I got asked multiple times how an iOS application using Uncle Bob’s Clean Architecture could look like. It is important to know that Clean Architecture is an abstract concept which is not related to any given technology or language. In other words: Clean Architecture is not defining how you have to structure your app in detail or forcing you to use any third party libraries.
There are already some iOS architectures inspired by Clean Architecture out there, like VIPER or VIP. When searching for Clean Architecture on iOS you may easily end up in either a very abstract explanation about Clean Architecture or in articles about VIP or VIPER using third party libraries. That is why I decided to write this article.

In this article, I will explain the concepts of Clean Architecture and how to use it correctly. In the first part, we will have a look at the idea of Clean Architecture and in the second part, we will create a small sample application. By implementing our sample application step by step, I like to further spread awareness how you could implement an application with Clean Architecture from scratch and how you could migrate any iOS application to Clean Architecture.

An introduction into Clean Architecture

Clean Architecture is a software architecture solution from Robert C. Martin also known as Uncle Bob. You may know him as author of the books Clean Code and Clean Architecture. If you do not know the books yet, I can highly recommend to read them.

When searching for Clean Architecture, you easily end up with a very abstract image showing different circles and some arrows.

Clean Architecture by Uncle Bob¹

The main idea of Clean Architecture is the separation of software into different independent layers. Every single circle in the image above represents one of these layers.

The overriding rule that makes sure that all layers are independent is the dependency rule, represented with the horizontal red arrows from the outer to the inner circle. In other words, nothing from the inner circle can know anything at all from the outer circle. To make the dependency rule a bit more catchable an example: Entities do not depend on anything and use cases only depend on the entities.

One of the biggest advantages following this architecture is the easy maintenance, modification and testability of applications. On top of this, everything can easily be changed because it’s highly decoupled from any UI, web framework or database.

You may ask yourself now, how can I implement different independent layers in my iOS application? This will become more graspable when having a look at the data flow.

Data Flow

In general there are multiple approaches out there dividing an application into different independent layers. The most common one is the classic three layer architecture: Presentation layer, domain layer and data layer.

  • Presentation Layer contains UI and Presenters
  • Domain Layer contains Use Cases and Entities
  • Data Layer contains Repositories and Data Sources
Clean Architecture overview and its request/response data flow inspired by Reso Coder’s Flutter Clean Architecture Proposal²

Let us have a look on the data flow with an example. Imagine we are opening an app that loads a list of news articles from a web api and displays the fetched articles to the user. The data flow would look like this:

  • View calls method in presentation logic (view model, presenter or similar)
  • Presentation logic executes one or more use cases
  • Use case calls repository to fetch data
  • Repository fetches data from data sources (cached or remote) and returns domain entity
  • Use case handles errors that might occur and returns domain entity that should be displayed
  • UI displays data

As you can see, the whole data flows from presentation layer through domain layer to data layer and then back to the presentation layer. Let us have a deeper look on the three different layers.

Presentation Layer

Views and presentation logic are located in the presentation layer.

Every view element the users can see or interact with are part of the view (which is typically written in SwiftUI or UIKit).

The presentation logic defines for example what happens if the user taps on a button. It should be considered as mediator between use case and view. It is being called from the view, executes the matching use cases and is completely view independent (see dependency rule). In other words: It should not contain any import UIKit or import SwiftUI what makes it also very easy to test.

Clean Architecture is not defining which pattern you have to use for the presentation layer. You could for example easily use Model-View-ViewModel (MVVM), Model-View-Presenter (MVP) or Model-View-Controller (MVC).

When having once again a deeper look at the dependency rule, you can also see that the presentation layer (outer layer) depends only on the domain layer (inner layer).

Domain layer

Use cases, domain entities and repository protocols are located in the domain layer. The domain layer is completely independent from any other layer.

You may ask yourself now, how can the domain layer be independent from any other layer when our use cases have to call repositories in the data layer? The answer is pretty simple. Our domain layer contains only the protocols of our repositories. With other words: We are only setting up a repository definition without knowing anything about the implementation. It is comparable with a contract between domain and data layer.

Use cases contain business logic of our application. A single use case handles only one business case. In the past, I made the experience that it is a good practice to stick to the rule that every use case contains only one single function called execute. Following this rule makes it easier to guarantee that every use case takes care about only one single business case.
Please pay attention that an use case should be completely independent from any other layer or framework. It should be easy to reuse any use case in another project.

Data Layer

Repositories, data models and data sources are located in data layer.

As already mentioned, repository protocols are located in the domain layer while the implementation of the repositories is located in the data layer. Only the repository knows if data should be fetched for example from user defaults, core data or any web APIs. Use cases do not care if data is being fetched asynchronous or synchronous. It is more a problem/complexity that the repositories have to deal with.

From my point of view, it is nevertheless much easier to directly use completion handlers for the whole response flow independent of having an asynchronous or synchronous data flow in place. In other words: Instead of using return values for functions, I recommend to directly use completion handlers for the response flow in the presentation, domain and data layer. We then do not need to add extra complexity to our data layer like for example RxSwift or DispatchGroup. I do not want to say that these are bad mechanisms but I think it is easier to write tests using completion handlers, especially when using swift 5. We can then for example easily use the new introduced Result type (comparable with the either/or pattern).

Let us have a look how Clean Architecture would look like in a real application.

Sample application

Screenshots of our sample application

We are going to build a small iOS application that is fetching news from an open accessible web api and showing all fetched articles to the user in a list. Users can then tap on single articles to get more details.

Project structure

When starting a new application from scratch, the first questions you may ask is how should the project be structured? There are two different approaches how to structure your project.

First approach:
.
├── data
│ └── authentication
├── domain
│ └── authentication
├── presentation
│ └── authentication

Create folders for the different layers domain, presentation and data layer on the root level. These folders should then contain subfolders for every type of functionality. If we for example would have something like logging in we could create a subfolder called Authentication.
I can recommend using this approach for smaller projects.

Second approach:
.
├── authentication
│ └── data
│ └── domain
│ └── presentation

Create folders for every type of functionality on the root level like for example Authentication, Dashboard or Basket. Every folder contains then subfolders called Domain, Data and Presentation for the logic of the different layers.
I can recommend using this approach for larger projects.

In our example, we will use the second approach even though we do not have a large project. I think doing so helps you to get a better overview about the advantages and disadvantages of both approaches.

Data Flow

When implementing a new app from scratch it is helpful to create an overview of the request/response data flow. Especially when you are not that used to Clean Architecture, it will help you a lot. Our sample application has only one single data flow. When the user is opening our sample application or is pressing the refresh button, we will fetch news from the web api and show the fetches articles to the user.

Fetching news request/response data flow

We know now, which components we have to implement. When starting with the implementation, I highly recommend to start with the most independent and stable layer, the domain layer.

Domain Layer

The most independent and stable component of our sample application is the ArticleEntity. We have to define which properties we need for our articles. This also depends a bit on the news web api that we are using. Let us first have a look on the JSON response of our news web api.

JSON response of our open news web api

We are only interested in author, content, title, description, url to the article, url to the image and when the article has been published. That is why our ArticleEntity then looks like the following:

We will now continue implementing the FetchArticlesUseCase. As already mentioned, use cases contain the business logic of our applications. Our app does not have much business logic in place but to make it a bit more interesting let us just imagine, we would have the following additional business requirement:

  • Only one request is being performed within 5 minutes to be able to stay with the free quote of the web api.

Our FetchArticlesUseCase will obtain ArticleEntities from our ArticleRepository if we did not fetch any articles in the last 5 minutes and passes the received domain entity to the presentation layer. That is why the protocol of our ArticleRepository (contract between domain and data layer) looks like the following snipped:

And the implementation of our FetchArticlesUseCase like the following snippet.

You may ask yourself now, why do we have a FetchArticlesUseCaseImpl and FetchArticlesUseCase protocol in place? This approach makes it much easier writing mocks and unit-tests. Feel free to check out the whole sample project (you will find a link at the end of this article) and have a look at its unit-tests.

As we can see, our FetchArticlesUseCase does not have any error handling in place but if we would like to track error occurrences, we could for example call another use case within the execute function that would then take care about tracking errors.

Data Layer

After the domain layer has been implemented, we can continue implementing the data layer. It contains mainly our NetworkRepository (used to access web API data sources), our ArticleRepository implementation and DataModels.

We already defined the protocol of our ArticleRepository in the domain entity. Because of this, we already know how the structure of our ArticleRepository should look like. Having everything in place, it looks like the following snippet:

As we can see, the ArticleRepository takes care in generating the right url components, calls the NetworkRepository, validates the data source specific response, generates error messages (without handling errors — this would be part of the use case), parses data model to domain entity and forwards the domain entity to the use case.

We now have domain entities and data models. You may ask yourself now, what is the difference between models and entities? Models are entities with some additional functionality on top of it. In our case, it is the Decodable protocol to be able to serialize and deserialize instances.

The NetworkRepository takes care to access the web api and can be reused from other repositories.

Presentation Layer

After we are done implementing the data layer, we can jump over to the presentation layer. In the presentation layer, we are using MVVM with SwiftUI and Combine but as already mentioned it is up to you what you are using here. It is only important to have a separation between the presentation logic and the view in place. We could for example also use MVP with UIKit instead of MVVM with SwiftUI.

Let us start with the presentation logic. We already know that the FetchArticleUseCase is being called by the presentation logic when the user either clicks on the refresh button or the view gets loaded.

The list of ArticleEntities that we are getting from the FetchArticlesUseCase will then be mapped to our presentation model ArticleListUiTile. It is not mandatory to always have a separate presentation model in place. In our case it makes sense to do so because we have to make sure our model fulfils an ui specific requirement (Identifiable protocol to be able to use it in a SwiftUI list).

The ArticleListView is our main view that is displaying all fetched news articles. We are using the ArticleListViewModel as presentation logic. If, for example, isLoading is set to true the view will be updated.

It is important to be aware about the fact that the presentation logic (in our case the view model) does not care how the view states (isLoading, isEmpty,…) are being shown to the users.
With other words: It does not care if we are showing a loading indicator, network activity indicator or something completely different.

Feel free to also check out the other views of the sample project (like ArticleDetailView) in the git repository.

Just like that we’ve created a simple news application that follows Clean Architecture with MVVM, SwiftUI and Combine in the presentation layer. As already mentioned, we could also easily implement the presentation layer using UIKit and another pattern like MVP or MVC.

Repository with our sample application

You can download the whole source code of our sample application on GitHub. If you have further questions, suggestions or feedback, feel free to reach out to me.

Conclusion

We’ve created a small sample application using Clean Architecture. As you can see, Clean Architecture is not that hard to implement and helps a lot to separate your source code into independent layers. This makes it very easy to test and maintain your application (feel free to check out the GitHub repository).

If you have any questions, comments or suggestions, feel free to leave a comment or send me a message. You can find me on Medium or LinkedIn.

Thank you very much for reading this! If you liked the article, please clap 😊
Cheers.

--

--