Introducing Clean Swift Architecture (VIP)

October 19, 2017
Posted in Clean Code
October 19, 2017 mihailsalari

A couple of years ago, all of the iOS apps were small containing less than 10 screens. The codebase was small, storyboards were working excellent, and it was easy to maintain your project. From an architectural point of view, MVC was doing a great job.

How about today?

Today, we are facing big technological advancements and an insane app market growth. In other words, apps are becoming big and complex. We are working on projects that contain 20, 30 or even 40 screens making it impossible to be maintained with MVC.

As technology moves forward, so should we (developers).

 

Recently, I really got tired from MVC and started looking for a new architecture. After a short research, I have noticed the Clean Swift architecture and instantly fell in love with it! This architecture was exactly what I was looking for.

About the Clean Swift Architecture

Clean Swift (a.k.a VIP) is Uncle Bob’s Clean Architecture applied to iOS and Mac projects. The Clean Swift Architecture is not a framework. It is a set of Xcode templates to generate the Clean Architecture components for you. That means you have the freedom to modify the templates to suit your needs.

In an MVC project, your code is organized around and grouped by models, views, and controllers. In Clean Swift, your project structure is built around scenes (or screens). Here is an example how does one scene looks like. In other words, we will have a set of components for each scene that will “work” for our controller. These are the components:

  • Models
  • Router
  • Worker
  • Interactor
  • Presenter
  • Configurator

Communication

The communication between the components is done with protocols. Each component will contain Input and Output protocols which will be used for receiving and passing data between them. Worker communicates with Interactor, then Interactor with Presenter and Presenter with ViewController.

I have also designed a Flow Diagram so you can get a visual representation of the relations between these components.

 

Clean Swift (VIP) Flow Diagram
Clean Swift (VIP) Flow Diagram

Models

We will store all the models related to the controller. The Models class will be related to each component, as you can see in the Flow Diagram. It will be of type struct and mostly it will contain Request, Response, and ViewModel structs.

import UIKit

struct TestModel{
    struct Fetch {
        struct Request
        {
            var itemId = 0
            var keyword: String?
            var count: String?
        }
        struct Response
        {
            var testObj: Test?
            var isError: Bool
            var message: String?
        }
        struct ViewModel
        {
            var name: String?
            var date: String?
            var desc: String?
            var isError: Bool
            var message: String?
        }
    }
}

For this example, let’s assume you are working with an API call on this scene. You will need the following structs:

  • Request – parameters that need to be sent to the API request.
  • Response – intercepts the response from the API and stores the appropriate data.
  • ViewModel – everything that you need to show to the UI is stored here. For example, your API returns objects with 10 parameters, but you only need to show 4 of them.

Router

This is a simple component. The router takes care for the transition and passing data (if necessary) between view controllers. Also, you can use segues, unlike the VIPER architecture where you can’t do that.

import UIKit

protocol TestRouterInput {
    func showSomeVC()
}

class TestRouter: TestRouterInput
{
    weak var viewController: ViewController!
	
    func showSomeVC() {
        viewController.performSegue(withIdentifier: "someVC", sender: nil)
    }
        
    // MARK: - Communication
    func passDataToNextScene(segue: UIStoryboardSegue)
    {
        // NOTE: Teach the router which scenes it can communicate with
        
        if segue.identifier == "someOtherVC" {

        }
    }
}

You can see the showSomeVC() function that will perform segue with a given identifier. The passDataToNextScene() will take care of the passing of data between controllers. I am sure you have met this before.

Speed up Swift compile time 

Worker

The Worker component will handle all the API/CoreData requests and responses. The Response struct (from Models) will get the data ready for the Interactor. It will handle the success/error response, so the Interactor would know how to proceed.

typealias responseHandler = (_ response:TestModel.Fetch.Response) ->()

class TestWorker{
      
    func fetch(itemId:Int!, keyword:String!, count: String!, success:@escaping(responseHandler), fail:@escaping(responseHandler))
    {
        // NOTE: Do the work
        //call network etc.
          let manager = YourApiManager()
      
          manager.fetch(itemId: itemId, keyword: keyword, count: count, success: { (data) in
                  let test = Test(JSON: data)                                                              
                  success(TestModel.Fetch.Response(testObj: test, isError: false, message:nil))
             }) { (error, message) in
                  fail(TestModel.Fetch.Response(testObj: nil, isError: true, message: message))
           }
    }
}

Interactor

This is the “mediator” between the Worker and the Presenter. Here is how the Interactor works. First, it communicates with the ViewController which passes all the Request params needed for the Worker. Before proceeding to the Worker, a validation is done to check if everything is sent properly. If everything is ok, the Worker returns a response and the Interactor passes that response towards the Presenter.

import UIKit

protocol TestInteractorInput
{
    func fetchItems(request: TestModel.Fetch.Request)
}

protocol TestInteractorOutput
{
    func presentFetchResults(response: TestModel.Fetch.Response);
}

class TestInteractor : TestInteractorInput
{

    var output: TestInteractorOutput!
    var worker: TestWorker!
  
    func fetchItems(request: LoginModel.Fetch.Request) {
        if request.itemId == nil || request.count == nil || request.keyword == nil {
            return output.presentFetchResults(response: TestModel.Fetch.Response(object: nil,isError: true, message: "Fields may not be empty."))
        }
        worker = TestWorker()
        worker.fetch(name: request.name, type: request.type, count: request.count, success: { (object) in
            self.output.presentFetchResults(response: TestModel.Fetch.Response(object: object, isError: false, message: nil))
        }) { (error, message) in
            self.output.presentFetchResults(response: TestModel.Fetch.Response(object: nil, isError: true, message: message))
        }
    }
}

Presenter

Now that we have the Response from the Interactor, it’s time to format it into a ViewModel and pass the result back to the ViewController. Presenter will be in charge for the presentation logic. This component decides how the data will be presented to the user. In the presentFetchResults() function you can see that I am handling 2 delegate methods. (1) handling success situation, (2) handling error situation.

import UIKit

protocol TestPresenterInput
{
    func presentFetchResults(response: TestModel.Fetch.Response);
}

protocol TestPresenterOutput: class
{
    func successFetchedItems(viewModel: TestModel.Fetch.ViewModel)
    func errorFetchingItems(viewModel: TestModel.Fetch.ViewModel)
}

class TestPresenter: TestPresenterInput {
    
    weak var output: TestPresenterOutput!
    
    // MARK: - Presentation logic
    func presentFetchResults(response: TestModel.Fetch.Response) {
        // NOTE: Format the response from the Interactor and pass the result back to the View Controller
        let viewModel = TestModel.Fetch.ViewModel(name: response.testObj?.name, date: response.testObj?.date, desc: response.testObj?.desc, isError: response.isError, message: response.message)
        
        if viewModel.isError{
            if let output = self.output {
                output.errorFetchingItems(viewModel: viewModel)
            }
        }else{
            if let output = self.output {
                output.successFetchedItems(viewModel: viewModel)
            }
        }
    }
}

Configurator

I would call this the “superclass” of the scene. This class has a simple but very important job. It initializes all the above-mentioned components. This is a singleton class. Let’s see how it looks like.

import UIKit

// MARK: - Connect View, Interactor, and Presenter
extension ViewController: TestPresenterOutput
{
    override func prepare(for segue: UIStoryboardSegue, sender: Any?)
    {
        router.passDataToNextScene(segue: segue)
    }
}

extension TestInteractor: ViewControllerOutput
{
    
}

extension TestPresenter: TestInteractorOutput
{
    
}

class TestConfigurator {
    // MARK: - Object lifecycle
    
    static let sharedInstance = TestConfigurator()
    
    private init() {}
    
    // MARK: - Configuration
    
    func configure(viewController: ViewController)
    {
        let router = TestRouter()
        router.viewController = viewController
        
        let presenter = TestPresenter()
        presenter.output = viewController
        
        let interactor = TestInteractor()
        interactor.output = presenter
        
        viewController.output = interactor
        viewController.router = router
    }

}

ViewController

We are done with the components. I hope that you have understood so far what is going on. But, we are still not done. This is the last step, and it’s about bringing the components to action. As you can see in the Flow Diagram above, the ViewController will communicate with the Interactor, and get a response back from the Presenter. Also, when there is a need for transition, it will communicate with the Router.

import UIKit

protocol ViewControllerInput
{

}

protocol ViewControllerOutput
{
    func fetchItems(request: TestModel.Fetch.Request)
}

class ViewController: UIViewController, ViewControllerInput {

    var output: ViewControllerOutput!
    var router: TestRouter!
    
    // MARK: - Object lifecycle
    
    override func awakeFromNib()
    {
        super.awakeFromNib()
        TestConfigurator.sharedInstance.configure(viewController: self)
    }

    override func viewDidLoad(){
        super.viewDidLoad()
        output.fetchItems(request: TestModel.Fetch.Request(itemId: 23, keyword: "bbb", count: "3"))
    }

    func successFetchedItems(viewModel: TestModel.Fetch.ViewModel) {
        print(viewModel.name)
        print(viewModel.date)
    }
    
    func errorFetchingItems(viewModel: TestModel.Fetch.ViewModel) {
        print(viewModel.message)
    }
}

If you remember, we have created 2 delegate methods in the Presenter Output protocol called successFetchedItems() and errorFetchingItems(). Those methods are passing us the ViewModel, so we can handle the two different situations separately.

Here is a screenshot of an example scene.

Conclusion

I hope that you have enjoyed this tutorial and that it helped you understand the new and exciting Clean Swift architecture. Tried to explain it as detailed as possible. I know that for most people MVC is the comfort zone, but once you get out of it and try something new you will be amazed by the results. Clean Swift contains a bit more coding and few more files than MVC, but that makes it super easy for maintenance and writing test cases.

If you have any questions regarding this architecture, please don’t hesitate to send me a comment below, and I would be glad to assist. Also, don’t forget to share this with your friends who are also struggling with MVC.

Thank you for your attention!

, , ,

Creative design and development from KIV

Get in touch with me!