How I Refactored a (Massive) MVC iOS App? (Part 2)

Tadeh Alexani
Formaloo
Published in
4 min readMay 10, 2020

Now that we refactored some of our UI components on part 1, it’s time to get back to our code and have some refactoring there.

One of the biggest problems I am facing and feeling this year is the Massive View Controller that I create because of combining different parts of my app in one place.

For example, all the routing processes, network calls, data sources, and delegates are in the same place and it causes huge controller files.

One of my View Controllers now has more than 600 lines of code! Don’t argue with me, I really want to fix that NOW! 😄

Refactoring Model Files

The next thing that bothers me a lot was the amount of constructor that builds when you have a model component with lots of property and you need all of them, take a look at this for example:

init(title: String, percent: Int, typeOfFood: String, typeOfFoodImg: String, date: String, time: String, originalPrice: Int, discountedPrice: Int, terms: String, cardColor: String, frontBackgroundImg: String,backBackgroundImg: String, slug: String, maxGuestsCount: Int, reviewed: Bool = false, status: String = “none”, isMemberExclusive: Bool = false, priceToPayPerGuest: Double = 0.0, days: String = “All Days”)

I know that there is a great solution in Swift like Codable but I want to introduce you to the Builder Pattern which is great too!

One of the cons of this huge constructor argument pattern is that you should keep the order when you want to create an object and so much more. So I decided to move to the Builder pattern to create my model files.

The intent of the Builder design pattern is to separate the construction of a complex object from its representation.

So if you have an object with lots of properties, you want to hide the complexity of the initialization process, you could write a builder and construct the object through that. It can be as simple as a build method or an external class that controls the entire construction process. It all depends on the given environment.

This is a sample model file after I moved to the Builder Pattern:

final class RecordCategory { let title: String let slug: String let iconUrl: String let notifsCount: Intinit(builder: Builder) { slug = builder.slug title = builder.name iconUrl = builder.iconUrl notifsCount = builder.notifsCount}
class Builder { let slug: String private(set) var name: String = "" private(set) var iconUrl: String = "" private(set) var notifsCount: Int = 0 init(slug: String) { self.slug = slug }
@discardableResult
func withName(_ name: String = "") -> Builder {
self.name = name return self }
@discardableResult
func withIconUrl(_ url: String = "") -> Builder {
self.iconUrl = url return self }
@discardableResult
func withNotifsCount(_ noOfNotifs: Int = 0) -> Builder {
self.notifsCount = noOfNotifs return self }
func build() -> RecordCategory { return RecordCategory(builder: self) }}

Please note that the builder implementation can vary on the specific use case. Sometimes a builder is combined with factories. As far as I can see almost everyone interpreted it in a different way, but I don’t think that’s a problem. Design patterns are well-made guidelines, but sometimes you have to cross the line.

Refactoring Table/Collection View CellForRowAt

I decided to move from MVC to MVVM to reduce the code I use to show & format my data.

First I recommend you go and search for MVVM architecture (which is introduced by Microsoft in 2005).

To migrate from an MVC app to an MVVM, first I refactored my table and collection cells cellForRowAt function. I moved every logic code and formatting from there to a new file called {ModelName}ViewModel.swift and have written them inside an init function. A View Model file contains every property from your model that you need to use and show in a specific view. For example, if you need to show just a title and description in a cell but your model contains dozens of properties, you just define “name” and “description” properties in your View Model file:

struct RecordCategoryViewModel { let iconUrl: String let title: String let notifsCount: Int let showingBadge: Bool init(model: RecordCategory) {  self.iconUrl = model.iconUrl  self.title = model.title  self.notifsCount = model.notifsCount  self.showingBadge = model.notifsCount > 0 }}

Then you can safely move all the logic and formatting code from cellForRowAt to your TableViewCell/CollectionViewCell setupWith function which should have a View Model input that you will assign in cellForRowAt.

TableViewCell file:

func setupWith(_ viewModel: RecordCategoryViewModel) { ImageHelper.loadImage(withUrl: viewModel.iconUrl, imgView: iconView) titleLabel.text = viewModel.title badge.isHidden = !viewModel.showingBadge if viewModel.showingBadge {  badge.text = "\(viewModel.notifsCount)" }}

cellForRowAt:

let cell = tableView.dequeue(DashboardTableViewCell.self, for: indexPath) as DashboardTableViewCelllet category = recordCategories[indexPath.row]let viewModel = RecordCategoryViewModel(model: category)cell.setupWith(viewModel)
return cell

What’s the next step?

I think this is enough for part 2 of our series of refactoring a huge MVC app. Next, we’ll move to put out some routing and networking code from our view controller files. See you in part 3!

--

--