On a quest for the separation of concerns

Photo by Jason Leung on Unsplash

On a quest for the separation of concerns

I've been experimenting with different design patterns to achieve cleaner code since day one. None of them are perfect for sure and at the end of the day the ultimate answer is "it depends on the project". Let me explain briefly what I prefer these days to achieve the separation of concerns with less boilerplate code in my projects.

If you are using MVC, you are probably using it wrong. There is "the correct way" of doing that or maybe "Apple's way" of doing that I should say. Since I prefer the programmatic approach when laying out the UI, ViewControllers are the ones who always suffer from that. We can solve this by adopting another view like below.

import UIKit

final class SomeViewController: UIViewController {
    ...
    private lazy var someView = SomeView()

    override func loadView() {
        super.loadView()

        someView.delegate = self
        self.view = someView
    }
    ...
}

Ok, we managed to get rid of unneeded layout code from our view controllers but how can we abstract the view logic from them? Before I continue on that, I'd like to show you how I manage the layout code for subviews in my container views.

import UIKit

// MARK: - UILabel+Ext.swift
extension UILabel {
    static var title: UILabel {
        let label = UILabel()
        ...
        return label
    }
}

// MARK: - SomeView.swift
final class SomeView: UIView {
    ...
    private lazy var titleLabel: UILabel = .title
    ...
}

We still need to add constraint code inside our container views. You may prefer SnapKit for more elegant code here but I don't like adding dependencies if don't need them. But my thoughts can change over the years I'm open to changes always. Another way of reducing constraints code is by creating generic extensions for sure.

So we came to a part separating view logic from the view controllers. This can be achieved by creating a ViewModel or Presenter. But none of them give us an answer to how to handle navigation logic. For navigating another screen, we still need to use view controllers. And this creates a problem called tight coupling. Every view controller knows about the one comes before and after them. It is not good. Of course, it is not a big problem for small apps but since we are on a quest for the separation of concerns it is better to handle that logic somewhere different.

import UIKit

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }
    func start()
}

We created a protocol to accept from our coordinators in our case there will be one coordinator for demonstration purposes. Why do we need a protocol here? Yes, protocols are not the obligatory part but they help us take advantage of polymorphism.

I'd like to mention dependency injection. Some say it is a 25-dollar term for a 5-cent concept. It is. But why do we need that? By injecting our dependencies we decouple our classes from each other. This way, we get more testable code in our apps and also we can easily change our implementation without telling anyone (I'm talking about the parts in our app that inject them not our team leads 😅).

protocol NetworkManager {
    func fetchItems() async throws -> [Item]
}

struct NetworkManagerImpl: NetworkManager {
    ...
}

// MARK: - AppCoordinator.swift
final class AppCoordinator: Coordinator {
    ...
    private let networkManager: NetworkManager

    init(networkManager: NetworkManager = NetworkManagerImpl()) {
        self.networkManager = networkManager
    }
    ...
}

If you are using a lot of dependencies you might want to consider creating some kind of container for that. This way you can get rid of the fat initializers problem.

I know I skipped some implementation details but you can research and gain more knowledge on them or you can ask here in the comments section belove. If you like the post please consider showing your appreciation in some way. Until next time 🤞