Refactoring a legacy iOS App

Peña Fernando
4 min readNov 4, 2022

--

Design Patterns and Clean Code

In this article we will refactor an App’s code little by little applying different design patterns (Dependency Injection, Repository and Adapter) pushing it towards a Clean Architecture state. I won’t be rewriting the App from scratch but refactoring it one step at a time.

Currencies App

Imagine that we inherit a code from a previous developer of a simple App that loads different currencies prices from a service and displays them on a screen.

This is a really simple App, it can be an iOS Dev interview exercise 😉

We open the project and check the code. You can download it from here, under initial-state branch.

The CurrenciesViewController is loading data from a service, decoding it and displaying it on a tableView. Those are a lot of responsibilities for a single component.

At first glance that’s not an issue, the App is working, isn’t it? But if you have worked on the same project for a while you should now that if you don’t follow some guidelines and principles the code can become really difficult to maintain and modify. It can also become difficult to read and follow for other developers, even to your future self.

What happens if the requirements change and we need to update our UI? (it looks awful by the way), if we need to change our data provider? Or displaying a loading state/error handling?

To modify this code we would have to run the App, test manually the different scenarios and hope we didn’t missed any… having automated tests would be really valuable, but it’s complicated to unit test this component, it currently has too many responsibilities and some implicit dependencies.

Abstracting the Presentation layer

Current ViewController responsibilities:

  • Fetching the data from the network (URLSession)
  • Decodes the response from data to CoindeskResponseDTO (JSONDecoder)
  • Maps the response to an array of currencies
  • Presents data to the user

The only one that corresponds to the Presentation layer is the last one. So let’s refactor our code.

Removing Networking & Decoding responsibilities

First we move all the Data providing related code into its own class (CoindeskApiClient). Changes of this commit are here.

Step 1 - Remove Networking and Decoding logic from the ViewController

Decoupling Presentation and Data code

Well, we removed the network and decoding code from CurrenciesViewController but it’s still referencing Coindesk’s code. If we change from datasource we still need to modify the UI and that’s not ideal, if we didn’t change how we are presenting data to the user the ViewController shouldn’t be touched.

ViewController has a strong dependency with the ApiClient

Dependency Inversion

We will invert that relation using Dependency Inversion principle, the Presentation layer won’t directly reference the ApiClient anymore. Instead it will reference an abstraction (a protocol in Swift) and the ApiClient will implement it, inverting the dependency.

Dependency inversion - Invert ApiClient dependency from the ViewController
Step 2 a - Dependency Inversion

We are in a better situation now, we inverted the dependency, didn’t we? If we look closer the ViewController it still references the data layer implementation, the protocol CoindeskCurrenciesDataSource uses Coindesk’s DTO models, so both layers are still tied. The ViewController depends on an abstraction, but that abstraction references the implementation indirectly, this is not a correct implementation of the Dependency Inversion principle.

Repository

To completely isolate both layers we will create a Domain one that will act as a boundary. Our Domain is where our App’s models live, it’s the core of our App. With the Domain layer on board we can use the Repository Design Pattern.

Repository - Domain layer isolates the Data and the UI layers
Step 2 b - Repository

Adapter

A good way to keep our CoindeskApiClient reusable is to keep it isolated and with no reference to your App’s code, that way we can reuse it on another project without having to modify it. It’s not weird to use an SDK or a library supported by our data providers instead of creating the ApiClient ourselves.

But we still need to connect both worlds, for that we can use the Adapter Design Pattern.

Adapter Design Pattern
Step 2 c - Adapter

Summary

We’ve refactored your legacy code little by little and ended up following Clean Architecture principles to have a clear abstraction between the Data and the Presentation layers, this way they are decoupled and can be developed, tested and maintained in isolation.

We can change/improve the code a lot more (our API client is almost dummy, we can migrate the presentation layer from MVC to MVVM, adding UseCases…) I just wanted to demonstrate how to start applying Clean Architecture principles little by little without needing to rewrite the App from scratch.

--

--

Peña Fernando

iOS Developer with 10 years of experience that never stops training. I’m passionate for well structured and reusable code.