iOS Clean architecture? My way on a very simple feature [MVVM, Repository, URLSession, Swift]

Wilmer Barrios
13 min readJul 18, 2022

--

Throughout the years, I’ve been in the pursuit of the best software development practices, hitting topics like Clean Architecture, Modularized code, Design patterns, Dependency injection, MCV, MVVM and a lot more topics that as a Developer (and for some Job Requiring people) have been for me like “Silver Bullets”; but in the end, software is always evolving, so these best practice terms are working but could change in the future.

I want to have a record/screenshot of how I’m doing software development at this right moment, being able to share it with the community for anyone that can find this useful, and more importantly, have feedback from anyone that, with another perspective, can see flaws or talk about their experience.

Illustrated by Ines Miselem https://www.behance.net/InesMiselem

What you will have at the end of the article?

  • An implementation of MVVM design pattern, in a very basic way without any Reactive framework.
  • A completely decoupled loading system for any source of data with a Repository approach.
  • A reusable HTTP GET client based on Foundation’s loading system URLSession.
  • An API definition to model the API.
  • And more importantly, we have created a decoupled feature that can be isolated, mocked, and tested on each layer.

I like to make improvements, studies, or simply set ground or conclusions to topics that I know or want to learn with easy and little requirements, that can get as complex as you want, and push myself to the limit. In this case, for example, a simple app that fetches random facts about lovely creatures like dogs is enough to demonstrate a point or expertise in a topic.

Here, you will find how I like to separate and decouple software, and, hopefully, you can learn something useful about it! (:

Planning

As a mobile application engineer or any front-end engineer, iOS Engineer in my case, it is very common to face a feature development without an active back-end service, so I start by defining what the Product Owner, or any client wants to achieve with her/his dream feature or app. So for now, the only thing I’ll mention is what you’ll hear in most job requirements: “I want an app that displays random facts about dogs, there’s no back-end service yet, but there will be”.

For some developers, starting to write an app would be unthinkable with this limited amount of information, but let me tell you, you can; and at this stage you don’t have to worry about changes in the requirements, UI, or even the data source of the random facts. This is a day to day duty for us as Software engineers, and one of the secrets for being a great developer is learning to create decoupled code, make your code work for you like small tools that don’t depend from any other component.

To achieve this, you will face topics like, “Avoid global state mutation”, “Dependency Injection”, and many more, that, in some way, will be implicitly present in this article, but the definition of these terms will be left out, as it is out of the scope of this article. Maybe we’ll see them in a next publication? We’ll see…

Project/Feature common layers

Almost every Intermediate/Advanced Software architecture article will mention the use of “layers”, so it can sound a little cliché to bring it up, but I find that it is harder to understand or internalize the concept until you find the “patterns” on multiple implementations. So I want to define them, at first glance, as big a picture, and then, throughout this article, as smaller and more specific duties. For now it can be seen like this:

For the time being, with the simple requirement mentioned before as an “App that fetches random dog facts” we can separate it into these:

  • The Datasource, which, at the moment, is a Rest API that will fetch a random fact.
  • The Repositories, will enable the actions (no business logic) or what you can perform to an entity.
  • The ViewModel will handle more concrete business logic requirements and will handle the “state” of the application or entity, and will respond or interact as it is. (On the table I’ve written MVVM/MVC, etc. because it won’t matter, all of them will achieve the same purpose)
  • The UI, which in iOS Case, can be a Storyboard, an xib file, or a programmatic UI; it is irrelevant, each of these has its pros and cons, but I personally prefer full programmatic UI.

Remember, each of these layers can change at any time, as the requirement does; for example, the data source can be a local finite database in the phone (static text file, a real DB like CoreData, etc.), it can be a remote REST API, a GraphQL service, in short it can change any time and your code has to be prepared for that.

In the case of UI, you can be working with a client that is still figuring out what’s the image they want to present to the public like colors, fonts, distribution, device screen sizes; all of these things that for us as Software engineers, might be not a very big deal, but the reality is they are. I’m not an expert on the UI/UX topic but just knowing the importance of it and that it can (and will) change, and that that’s why there’s a need of a decopled UI from any busines logic or implementation details, is more than enough for now.

You might be asking yourself “why would it change?, is it to make the developer’s life harder?”, and that’s a valid question, I’ve even asked it myself before; but the answer is no. Companies and clients make these decisions because it was the most suitable option at the moment. For example a startup can start with services like Firebase for an MVP, but when the app is released, the costs to maintain the app running grow more and more, so there might be a need to change to more suitable services or infrastructure at any time.

Creating the application

Datasource — Remote Network Layer

As we know, the requirement is a restful API, which means it will need an HTTP Client to handle everything related to the connectivity; the requirement is very simple, we will have a decoupled code but won’t cover most of the edge case at the moment. Remember, do it simply but taking into consideration it can change any time.

When a REST API gets into the conversation I immediately think of these characteristics that, in my opinion, all of my API clients should take into consideration:

  • Environments: Dev, Staging, Production, Preprod… or any environment that you could have, as everything in Software can change.
  • Endpoints: Each exposed service, in this case, dog facts, have other properties like:
  • HTTP Methods: GET, POST, PUT, DELETE, etc. For now, just the GET method will be needed.
  • Possible Response Status codes: 200, 201, 404, etc. Not all APIs have defined responses for all HTTP status codes, and it helps to have better and specific error/warning user-readable messages (without being too technical) to describe what’s happening with the application. For now, we won’t take these into consideration.
  • Authentication: OAuth client, JWT, tokens expiration, etc. We won’t go very deep into this, I could write another article about how I handle this in the future.
  • Response bodies: JSON, XML, or whatever format it could reply to.

For the moment, I won’t expose which service, base URL, or response body we will have; just to clarify the point that this is not needed right now, this is an easy way that determine your app is not that highly coupled with a specific service.

HTTP Client Protocol

Why a protocol? Why Result type? Why the Data type? This and many more questions can come up, this is why I want to explain line by line (such as Raywenderlich.com likes to do):

  1. Foundation is required because base types like URL and Data don't exist as part of Swift Standard Library, they are part of Foundation Framework.
  2. A protocol (or interface in other programming languages) is one of the best ways to decouple specific details or implementations, and has the great possibility of adding, modifying, and removing behavior in clean ways. It enables your code to be Software design pattern friendly if you use them correctly, but that can be a topic for another article.
  3. A typealias in this case gives more context and reusability for the response type of the HTTPClient. The Result enum type, gives an intuitive and contextual way to represent the basic "Succeed/Failure" states for any use case that requires it (not just HTTP requests). Finally Data is the common/base type to represent information in any way, no matter if it is JSON, XML, a Serializable Object or anything.
  4. For now, the requirement is just a fetch application, so a get functionality is the only condition. Remember, only make the necessary effort for the requirement at this moment.

Creating a Concreete type for our HTTPClient

We will be using the Foundation URL Loading System, in which the base type is URLSession class. For naming, I like to have the framework-related name, and what role it has in our application; in this case, it will be URLSessionHTTPClient.

Naming is important, with that name anyone contributing to the project can identify we are using URLSession class as our base loading system, taking the role of HTTPClient and will have exposed just the get method that conforms it. If you were using another URL Loading System like Alamofire you could define it as AlamofireHTTPClient class in your application; these instances could be used for different purposes and the implementation wouldn't depend on which library is being used.

This will be a more contextual Error type of what could be happening on our URLSessionHTTPClient, the error handling will be very simple and generic but exposes every possibility for future changes.

  1. The session reference is exposed throughout the constructor because it can change to a custom session to handle more complex HTTP requirements in the future, currently, it has a default value of the regular users .shared instance.
  2. As the URLSession handler exposes three optional values: Data?, URLResponse?, Error?, this means one, two, any or all of them can be nil. So, the possibilities make the processing of response very complex, I've extracted that processing to a helper static function that can be tested outside a real request in the future.
  3. This function will process and interpret if the response is valid, for now, our “valid” response criteria is just to have data value, error == nil and the response can be cast as valid HTTPURLResponse, this enables future changes interpreting response headers, response status codes, etc.

Repository Pattern: Decoupling “What You Can Do” with your entity from details.

On this layer, you want to decouple the capabilities of this entity from any data source, this means you will have “something” that will provide a random dog fact, understanding that “something” can stand for anything (Rest API, GraphQL Service, Local DB, InMemory Mocked instance, etc.), as previously mentioned.

DogFactsRepository definition

We now need a model for the dog fact at the repository level to expose it, I like to have it as struct with a Data suffix that doesn't contain any optional property; at the moment, each property definition is not required, so it will be just a simple definition that will work as a template.

A repository-related error is good to have, in order to find context-related issues; it won’t represent data source-related specifications, like network or DB reading errors, instead, you just know “It couldn’t be fetched”.

And now, the Repository definition:

As mentioned above, it is “something” that will getRandomFact each time you call it.

DogFactsRemoteRepository definition (almost)

For now, we know our data source will be a remote HTTP resource, so, in my opinion a concrete class name like DogFactsRemoteRepository describes it very well, but to have it as "concrete API proof" we have to define some entities beforehand.

Environment and API definitions

Reiterating, having an HTTP client data source, needs some capabilities, like environment and endpoint definitions, and I like to have them like this:

This could be defining a development, staging, or production environment and the implementations wouldn’t know it.

A DogFactsAPI will have endpoint definitions for the environments.

Now, we need an entity that will represent our remote data, that’s when the DTO (Data Transfer Object) term comes to the rescue. I like to have the suffix DTO for any entity that has this responsibility, and will be generated from any Data instance (as our HTTPClient fetches the data); for this, Swift has a great protocol to conform in order to achieve that.

The Decodable protocol extension, enables an entity to be constructed from a Data object only if every property of the definition conforms to Decodable too; almost every base Swift type conforms to it, for this case, all JSON representation base types like String, Bool, Int, Double, etc. conform to it.

As we don’t yet know the JSON Response body, we can define it as an empty type for now.

DogFactsRemoteRepository concrete definition (FINALLY!)

And now, finally, our DogFactsRemoteRepository definition:

Explanation:

  1. Injectable dependencies, an abstraction of HTTPClient as this concrete DogFactsRepository is related to networking and the data source of endpoints related to this entity.
  2. Actual request, to some HTTPClient and response handling.
  3. Response handling and parsing, this is where our *DTO and *Data objects will be interacting; the httpClient provides a Data value, the repository parses it as DogFactDTO if it can, and a mapper computed property 5) converts it into a non-optional values entity.
  4. Helper generic function to parse a Database type to any Decodable entity.
  5. Mapper computed property that converts the DTO into a usable *Data model.

Business Logic — View model definition

Now, we will need some entity that interprets user interaction and will trigger the fetching of the information, act accordingly, and notify us what’s happening with the execution. The MVVM pattern dictates that the ViewModel entity strictly doesn't know who is receiving these notifications, it can be a UIView, a UIViewController, a UITableView, etc. and the ViewModel wouldn't know it. As the requirement is very simple, we won't use any fancy reactive framework like Combine or ReactiveSwift, for now, a couple of closures will get the job done: onSuccess: (_ factValue: String) -> Void and onError: (_ errorMessage: String) -> Void definitions.

Explanation:

  1. Injectable dependencies, the repository that will be the data source, and the destination notification callbacks.
  2. The exposed method that will trigger the fetching of the data.

The API definition

Now, after having all this logic defined, our client has sent us an email telling us that our development API is active! And here’s the definition.

URL:         https://dog-api.kinduff.com/api/facts
HTTP METHOD: GET
NO AUTHENTICATION REQUIRED

SUCCESS RESPONSE BODY SAMPLE:

{
"facts": [
"Pitter patter. A large breed dog’s resting heart beats between 60 and 100 times per minute, and a small dog breed’s heart beats between 100-140. Comparatively, a resting human heart beats 60-100 times per minute."
],
"success": true
}

This is great! We have some code to update.

Define a concrete Environment

Updating our API Definition

Endpoint

DTO Model:

I like to have a JSON response sample on my DTOs’ documentation, this way if there’s a problem, you can easily understand what the DTO was expecting at the time it was introduced into the application, and maybe, a faster bug finding. I don’t consider this as bad practice, because if the remote service changes, your DTO related to that service has to change, and the sample response JSON in your documented code has to be updated on your PR ;).

In addition to all of that, you will have a beautiful preview of the response when you inspect the type, like this one:

As our DTO has changed, our DTO to Data model mapping has to change in order to provide the related information.

Data Model:

Composition of Random Dog Fact

Great! We are almost there, but now we just have multiple small and very role-defined entities; but where is everything we just made going to be connected or constructed? This “glue” or composition is a responsibility, so we will need a class, a struct, an enum or even a func that has that responsibility; as this is a small feature, I'll keep it simple with just a couple of factory methods:

  1. As you can see, we don’t want to couple any implementation of makeRepository() factory method to a concrete instance.

Creating the UI

We won’t have any UI Requirements for now, as that will be for another publication, but all entities that have been constructed throughout this article don’t need to know about the UI; it could be a UICollectionView a UIViewController, or just a simple UIView and none of the previous elements would require a change. That's the beauty of having all of this planned and created, since with this, a simple Storyboard with stock UI Builder elements (UITextView and UIButton) will get the job done.

And here is the definition for our ViewController class.

As you can see, a method has been added to the viewModel, to work as the input that represents the user interaction.

So now, I can run the application and see the finished product!

Or… wait.

Application threading management

As we know, and as Apple documentation always reminds us, all UI updates have to be on the main execution thread. We can decouple this responsibility in multiple ways, but for now, injecting this into our RemoteRepository will do the trick; having this, neither HTTPClient, viewModel nor ViewController need to have this responsibility! Only the RemoteRepository.

Final Results

Great! Right? Our UI might seem a little disappointing, but that will be a topic for another article, so stay tuned. ;)

Conclusions

What we have achieved:

  • An implementation of MVVM design pattern, in a very basic way without any Reactive framework.
  • A completely decoupled loading system for any source of data with a Repository approach.
  • A reusable HTTP GET client based on Foundation’s loading system URLSession.
  • An API definition to model the API.
  • And more importantly, we have created a decoupled feature that can be isolated, mocked, and tested on each layer.

Now that you have seen how I work on a day-to-day work basis, how I try to have independent blocks of code, and the usage of multiple patterns that I’ve learned throughout all my experience since 2017, I hope you find this useful and of course, if you have a different approach to achieve the same goal, please leave a comment! We are all discovering new ways to deliver excellence in our projects.

Thanks for reading!

GitHub Repo

Powered by: https://kinduff.github.io/dog-api/

References

--

--

Wilmer Barrios

https://wantcode.io/ Senior iOS Swift/Obj-C Engineer, ex-Full Stack Engineer (Python, PHP, JS, Java.)