How to extract functionality from your iOS legacy code

Daniel Torres
6 min readMay 30, 2021

Have you always wondered how could you extract any existing code into another app in a way that’s comprehensive and repetitive? In the following article, I’ll show how to extract any functionality from a legacy code with an example: In this case, the download of an Array of URLs into UIImages from one of my side projects.

Bad Code

So the problem was that I wanted to download each image from a given array of products and update the view as they arrive from the service one by one. Also if the user asks for another array of products, the app should stop any current downloads and create new downloads for the next set.

The current logic of this implementation looked like this.

Shows the Dependencies Diagram before the Code Extraction.

The ListOfProductsPresenter was implementing a protocol that prepares the view data for the view and needed two more objects a model (ListOfProductsPresenterModelProtocol) from which to extract the URLs from and the view (ListsOfViewDataProducts) to tell it to update after the view data is prepared.

ListOfProductsPresenter the stored objects and the objects needed to be instantiated.

The logic of that ListOfProductsPresenter related to the download of images was the following:

1. Extract the URLs from the images of an array of products.
2. Create a URLSessionDataTask from each URL and store them so I can cancel them in the future.
3. Create the logic to update the view with the needed image as each one arrive.
4. Cancel any tasks when new lists of products need to be presented to the view.

Shows how the data was prepared.

You could appreciate that the downloading of images was being done at the bottom and it was canceling any previous data tasks before doing so.

Shows how tasks were canceled, the logic to retrieve images, and the logic to update the view.

This approach was making these issues:
> I was breaking the SR (Single responsibility) principle, this class wasn’t only preparing the data for the view it was also executing network tasks and managing their state.
> I was breaking the OC (Open-closed) principle, which dictates that a class should be closed for modification and open for extension. i.e. every time I needed to modify how the images were downloaded I needed to go and modify this class.
> I was breaking the Dependency Inversion principle which dictates that higher classes should depend on abstractions rather than concrete classes. In this case, I was relying directly on the singleton of URLSession to download the images, DispatchQueue.main to update the view in the main thread, and URLSessionDataTask to store the data tasks.

First Step

So the first thing I did was to move all the network logic into its own class. I did this by creating a protocol that represented the needed intention, in this case, I created the ImageDownloaderProtocol.

shows the needed intent of the presenter.

So any type which received this message will get two things.
For one an array of ProductProtocol to get the URL images from the products and then a completion handler that executes when an image arrives from the network.

I created an ImageDownloader which implemented this protocol and handled the network tasks. This class did exactly what the ListOfProductsPresenter wasn’t made for. It had the following:

1. Extract the URLs from the images of an array of products.
2. Create a URLSessionDataTask from each URL and store them so I can cancel them in the future.
3. Execute a closure with the needed image as each one arrive.
4. Cancel any tasks when new lists of products need to be presented to the view.

Exactly the actions related to the network part of my requirements.

Shows the ImageDownloader logic. Where the network and download of the images reside from the first step.

So how does ListOfProductsPresenter call this method? by adding property injection to ListOfProductsPresenter. This is done by adding a stored property that conforms to ImageDownloaderProtocol which represents the needed intention.

Represents the imageDownloader property injection.

The completion handler passed to the imageDonwloader’s getImages message, handles the update of the view by calling the presentableView’s present(imageViewData in the main thread.

Shows how the imageDownloader calls the needed intent.

I also had a compositional root where the ListOfProductsPresenter was created. So all I needed to do was to initiate the ImageDownloader and inject it into the ListOfProductsPresenter’s imageDownloader property.

Shows how the ListOfProductsPresenter was created and how the ImageDownloader is injected.

The result of this approach let me inject any kind of way on how to get the images from. You can see it in a Diagram.

Represents the Dependencies of the different types from the First Step.

As you can see the ListOfProductsPresenter depends on abstraction instead of a concrete type and I use ImageDownloader to implement that abstraction and inject it into the ListOfProductsPresenter.

But there is another problem, ListOfProductsPresenter still depends on DispatchQueue and I can only use the logic of downloading the images by using ImageDownloader, i.e. we can’t really re-use the logic used to download the images in another app.

Second Step

In order to get that logic and remove the DispatchQueue from the presenter, I had to use an Adapter. This way, the presenter only needs to call its imageDownloader and doesn’t need to update the view on the main queue.

Creating an Adapter must conform to the needed intent, that is the ImageDownloaderProtocol. But instead of having the network logic inside, it will use a concrete type to delegate that responsibility.

So I extracted the network logic again from the previous implementation of ImageDownloader and created MultipleURLDownloader. This class doesn’t know and doesn’t need to know, anything about Products or Views. It only cares about managing the network actions given multiple URLs.

Note: It doesn’t use the main thread inside the completion handler executed after a data task finishes because it doesn’t know about views to update. Remember it only cares about the network.

Shows the MultipleURLDownloader which represents the network logic.

The adapter extracts the needed URLs from the products so the downloader can start and accepts a completion handler that executes in the main thread after its downloader finishes to retrieve the data of the image between each data task.

Shows the logic inside the ImageDownloaderAdapter

The last piece of the puzzle is to initiate the ImageDownloader and inject it into the ListOfProductsPresenter.

Shows how the ImageDownloaderAdapter is injected into the ListOfProductsPresenter.

Finally, The ListOfProductsPresenter is ready to remove the DispatchQueue dependency from its getImages func.

shows the final implementation of getImages.

The final Diagram looks like this…

Represents the Dependencies Diagram of the Second Step

Conclusion

We slowly extract the network logic tasks from the presenter by following SOLID principles to detect what was wrong in our design, then using dependency inversion so the presenter depends on abstraction, and finally use an Adapter to extract completely the needed network logic to be used in another app.

Hopefully, this will get you an idea of how to extract functionality from your legacy code and follow a clean code path.

This article was inspired by the Essential Developer Academy sessions.

Note: check the original article in my site:
https://www.danieltorresblog.com

Regards,

Daniel Torres

--

--

Daniel Torres

iOS developer - Delivering exceptional results no matter the situation.