• 15 min reading time

28 May 2020

Write Clean Code by Namespacing Your Models in Swift

MVVM and Clean Swift UI Design pattern examples


I have been using namespaced models a lot recently in my projects and at work, so I decided to share the idea in this article.


Image by Monoar Rahman Rony


One of the challenges when trying to write clean and modular code is deciding how to declare our models. Models may be requests or responses from an API or simple locally created view models that we can pass to a view to configure it.


In this article, we will explore how you can use Swift’s caseless enums to easily namespace your models. But first, why would you even bother namespacing your models?


Namespacing models comes with two main benefits:


  1. Cleanliness. To write clean and modular code, we want to make our models distinctly belong to their module and be recognized almost instantly while looking at the code.
  1. Avoiding mistakes further into development. Not confusing one model for another.
    Let’s say you have a screen that shows a list of cars.

    You would want to have a car model available to your whole application/framework. However, the API response model will be specific to your cars list screen. Our cars list module will also need a view model that will be specific to the cars list screen. As they are specific to the cars list, both the response and view models could be neatly namespaced and distinctly belong to the cars list module since we use them only in that context.

    Let’s see examples of the above using two architectural patterns you might be working with: MVVM and Clean Swift.

MVVM Example


import UIKit

struct Car {
    let colour: UIColor
    let name: String
    let brand: String
}

// Declaring MVVM module/screen
enum CarsList {
    
    // Declaring namespaced event/action
    enum LoadingCars {
        
        // Declaring namespaced response for LoadingCars event
        struct Model {
            let cars: [Car]?
        }

        // Declaring namespaced view model for LoadingCars event
        struct ViewModel {
            let carNames: [String]
            // easily add new properties in here if you need the view to bind to a more complex viewModel
        }
    }
    
    enum SelectedCar {
        
        struct Request {
            let index: Int
        }
        
        struct Model {
            let car: Car
        }
    }
}

class CarsListViewController: UIViewController {
    
    var viewModel: CarsListViewModelling?
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup() {
        viewModel = CarsListViewModel()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        viewModel?.loadCars { [weak self] in
            // call as: self?.viewModel?.carsViewModel?.carNames
            // refresh data in your table or collection view
        }
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        viewModel?.didSelectCar(request: CarsList.SelectedCar.Request(index: indexPath.row))
    }
}

protocol CarsListViewModelling {
    func loadCars(_ loaded: (()->()))
    func didSelectCar(request: CarsList.SelectedCar.Request)
    
    var carsViewModel: CarsList.LoadingCars.ViewModel? { get set }
    var selectedCar: CarsList.SelectedCar.Model? { get set }
}

class CarsListViewModel: CarsListViewModelling {
    
    private let model: CarsListBusinessLogic?
    
    // Ideally in MVVM this would be an Observable property using Combine, RxSwift or similar framework
    var carsViewModel: CarsList.LoadingCars.ViewModel?
    var selectedCar: CarsList.SelectedCar.Model?
    
    private var cars: [Car] = []
    
    init() {
        model = CarsListModel()
    }
    
    func loadCars(_ loaded: (()->())) {
        model?.loadCars(loaded: { [weak self] model in
            guard let self = self else { return }
            
            if let cars = model.cars {
                self.cars = cars
                let carNames = cars.map { $0.name }
                self.carsViewModel = CarsList.LoadingCars.ViewModel(carNames: carNames)
              
                // in MVVM we wouldn't need a closure here if carsViewModel is an observable property
                loaded()
            }
        })
    }
    
    func didSelectCar(request: CarsList.SelectedCar.Request) {
        let car = cars[request.index]
        selectedCar = CarsList.SelectedCar.Model(car: car)
    }
    
}

protocol CarsListBusinessLogic {
    func loadCars(loaded: ((_ model: CarsList.LoadingCars.Model) -> Void))
}

class CarsListModel: CarsListBusinessLogic {
    
    func loadCars(loaded: ((_ model: CarsList.LoadingCars.Model) -> Void)) {
        // here you would retrieve cars from a remote API or database
        let cars = [Car(colour: .black, name: "DB11", brand: "Aston Martin"),
                    Car(colour: .white, name: "Model X", brand: "Tesla"),
                    Car(colour: .green, name: "GT500", brand: "Mustang")]
        
        let model = CarsList.LoadingCars.Model(cars: cars)
        loaded(model)
    }
}

You can see that alongside CarsListViewController, there is the CarsList enum with the LoadingCars nested enum that has Model and ViewModel structs.


So now your view model type can become CarsList.LoadingCars.ViewModel. If you compare that with a top-level struct named CarsListLoadedViewModel, you can notice a benefit of nesting your models and namespacing quickly.


Now to add new event models, you can add a new nested enum inside CarsList called SelectedCar that has its own Request and Model structs.


In this example of namespacing, your view doesn’t know of all the cars that you parsed from the back end/database (Single Responsibility principle in SOLID). It only knows what it has to know — carsViewModel: CarsList.LoadingCars.ViewModel to configure the list and selectedCar: CarsList.SelectedCar.Model, which you can then use to pass to a different detail screen.



Clean Swift Example

Let’s also have a look at the same cars list example using Clean Swift architecture.


import UIKit

struct Car {
    let colour: UIColor
    let name: String
    let brand: String
}

// Declaring Clean Swift module/screen
enum CarsList {
    
    // Declaring name-spaced event/action
    enum LoadingCars {
        
        // Declaring name-spaced response for LoadingCars event
        struct Response {
            let cars: [Car]?
        }
        
        // Declaring name-spaced view model for LoadingCars event
        struct ViewModel {
            let carNames: [String]
            // easily add new properties in here if you need to pass them from presenter to view
        }
    }
    
    enum SelectedCar {
        
        struct Request {
            let index: Int
        }
        
        struct Response {
            let car: Car
        }
        
        struct RoutingModel {
            let car: Car
        }
    }
}

protocol CarsListDisplayLogic: class {
    func showListOfCars(viewModel: CarsList.LoadingCars.ViewModel)
    func showSelectedCar(routingModel: CarsList.SelectedCar.RoutingModel)
}

class CarsListViewController: UIViewController, CarsListDisplayLogic {
    var interactor: CarsListInteractable?
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup() {
        let viewController = self
        let interactor = CarsListInteractor()
        let presenter = CarsListPresenter()
        viewController.interactor = interactor
        interactor.presenter = presenter
        presenter.viewController = viewController
    }
    
    override func viewDidAppear(_ animated: Bool) {
        interactor?.loadCars()
    }
    
    func showListOfCars(viewModel: CarsList.LoadingCars.ViewModel) {
        // use viewModel.carNames in your table or collection view
    }
    
    func showSelectedCar(routingModel: CarsList.SelectedCar.RoutingModel) {
        // push a detail view screen and pass routingModel.car object to it
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        interactor?.didSelectCar(request: CarsList.SelectedCar.Request(index: indexPath.row))
    }
}

protocol CarsListInteractable {
    func loadCars()
    func didSelectCar(request: CarsList.SelectedCar.Request)
}

class CarsListInteractor: CarsListInteractable {
    var presenter: CarsListPresentable?

    private var cars: [Car] = []
    
    func loadCars() {
        // here you would normally retrieve cars from a remote API or database
        cars = [Car(colour: .black, name: "DB11", brand: "Aston Martin"),
                Car(colour: .white, name: "Model X", brand: "Tesla"),
                Car(colour: .green, name: "GT500", brand: "Mustang")]
        
        let response = CarsList.LoadingCars.Response(cars: cars)
        presenter?.presentListOfCars(response: response)
    }
    
    func didSelectCar(request: CarsList.SelectedCar.Request) {
        let response = CarsList.SelectedCar.Response(car: cars[request.index])
        presenter?.presentSelectedCar(response: response)
    }
}

protocol CarsListPresentable {
    func presentListOfCars(response: CarsList.LoadingCars.Response)
    func presentSelectedCar(response: CarsList.SelectedCar.Response)
}

class CarsListPresenter: CarsListPresentable {
    weak var viewController: CarsListDisplayLogic?
    
    func presentListOfCars(response: CarsList.LoadingCars.Response) {
        guard let carNames = response.cars?.map({ (car) -> String in
            return car.name
        }) else { return  }
        
        let viewModel = CarsList.LoadingCars.ViewModel(carNames: carNames)
        viewController?.showListOfCars(viewModel: viewModel)
    }
    
    func presentSelectedCar(response: CarsList.SelectedCar.Response) {
        let routingModel = CarsList.SelectedCar.RoutingModel(car: response.car)
        viewController?.showSelectedCar(routingModel: routingModel)
    }
}

If you’re not familiar with Clean Swift UI design pattern, you can read more about it in its handbook.


As with the MVVM implementation, you can see that alongside CarsListViewController, there is the CarsList enum with the LoadingCars nested enum that has Response and ViewModel structs.


Now we can easily construct the response in CarsListInteractor and pass it to CarsListPresenter. Then, using that response, we create a viewModel in CarsListPresenter and pass it to CarsListViewController. The VIP (view-interactor-presenter) cycle LoadingCars is now complete.


With a SelectedCar nested enum inside CarsList, we can create Request, Response, and ViewModel structs to pass around in the SelectedCar VIP cycle.


Conclusion

With namespaced models, it should be easier to maintain the distinct models that we pass around in our code. In my time using namespaced models, I also found that it’s quick to spot what the code does and easy to change a model in the future if requirements change.


I hope you enjoyed this article and learned something new on the way.


If you have any thoughts on this article, I would love to hear them!
You can always get in touch with me via Twitter or LinkedIn 🙂


Cheers,


Tim