iOS Development

Understanding the Architecture of iOS Apps with VIPER

Jul 30, 2019

Blogger-Picture
Prakunj Chaudhary
Software Developer
Blog-Banner

Different design architectural patterns form an essential part of making the core of your app strong. How we shape our buildings is what later dictates how we live and grow, in a similar fashion it is how you architect your app that defines how you will handle the growth of the app.

But why is the architecture of an app so important to take care of and where does VIPER fit into this dynamic?

The process of designing the code in a way that makes every piece easily identifiable, with specific and oblivious purposes and ensures that everything fits together to work as a piece of perfectly oiled machinery with logic is software architecture.

The architecture of the application is the driving force behind the minimization of code duplication, preventing high coupling and providing a general solution for recurring problems through a common way of writing code.

Introduction To VIPER

Most iOS apps are designed using the MVC model.

Often noticed during the development of iOS apps designed using the MVC model is that the logic goes in the UIViewController's subclasses. This is referred to as the Massive ViewController problem.

This problem arises as the View Id is concerned with drawing itself on the screen. Model is the data storage and that leaves the ViewController to do the bulk of the job. Because the ViewController does the bulk of the processing these codes need to be then slimmed down to improve the functionality of the app.

Further affected are also the testability, maintainability, and refactoring speed.

iOS is a highly dynamic platform as Apple releases new iOS every year, maintenance and refactoring of the ViewController become essential.

If an app has issues with the maintenance it will lead to low desirability by the consumers. Testability is the answer to the regressions that may occur in a new device & VIPER is the solution to these problems.

What Is It?

VIPER is the backronym for View, Interactor, Presenter, Entity, and Router. It is the application of clean architecture to iOS apps. Unlike most other patterns which are more like UI patterns, Viper thinks about the whole architecture. Designed via distinct layers it separates the application logic from the domain model logic creating a clean framework for better management of the code.

Structure of Viper

Structure of Viper

Main Components

View

As is the case with MVP this is the class that holds all the code to show on the app's interface to the user and then records their interaction with them. It communicates through the protocol with the presenter that is at a higher level than that of the UI classes. The view and presenter use separate structures to share data amongst each other. This class only enables one to view logic.

  
//MARK: View Interface
protocol FeedsViewControllerInput {
    func displayFeeds(_ viewModel: FeedsViewModel)
    func displayFeedsFetchError(_ viewModel: FeedsViewModel)
}

protocol FeedsViewControllerOutput {
    func fetchFeeds(_ request: FeedsFetchRequest)
}


//MARK: View
class FeedsViewController: UITableViewController {
    
    var output: FeedsViewControllerOutput!
    var router: FeedsRouter!
    var feeds: [Feed] = []

    // MARK: Object lifecycle
    override func awakeFromNib() {
        super.awakeFromNib()
        FeedsConfigurator.instance.configure(viewController: self)
    }
    
    // MARK: View lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchFeedsOnLoad()
    }
}

//MARK: ViewController Interface Conformance
extension FeedsViewController: FeedsViewControllerInput {
    
    func displayFeeds(_ viewModel: FeedsViewModel) {
        if let feeds = viewModel.feeds {
            refresh(feeds: feeds)
        }
    }
    
    func displayFeedsFetchError(_ viewModel: FeedsViewModel) {
        if let error = viewModel.error {
            print(error)
        }
    }
    
    func refresh(feeds: [Feed]) {
        self.feeds = feeds
        tableView.reloadData()
    }
}

  

Interactor

This is considered to be the backbone of the app as it contains the business logic as defined by the user. It handles data fetching and abstracts the data store.

  
   //MARK: Presenter
class FeedsPresenter: FeedsPresenterInput {
    weak var output: FeedsPresenterOutput!

    func presentFeeds(_ response: FeedsFetchResponse) {
        processPresentation(for: response.feeds!)
    }
    
    private func processPresentation(for feeds: [Feed]) {
        //preform data formatting if needed
        let viewModel = FeedsViewModel(feeds: feeds, error: nil)
        output.displayFeeds(viewModel)
    }
}



  

Presenter

This records the response from the view and works accordingly. This is the only class that interacts with all others. This connects the view and the interactor.

  
    //MARK: Presenter Interface
protocol FeedsPresenterInput {
    func presentFeeds(_ response: FeedsFetchResponse)
}

protocol FeedsPresenterOutput: class {
    func displayFeeds(_ viewModel: FeedsViewModel)
    func displayFeedsFetchError(_ viewModel: FeedsViewModel)
}


  

Entity

This holds the basic model objects used by the interactor. It is the smallest element inside VIPER.

  
   struct FeedsFetchRequest {
    var email: String?
    var password: String?
}

struct FeedsFetchResponse {
    var feeds: [Feed]?
    var error: FeedsFetchError?
}

  

Router

It inputs data from the presenter and executes that action. This class contains all the navigation logic describing the screens that need to be shown. It also decides the actual order of navigation. This is written as a wireframe.

The components of VIPER provide the developer the freedom to be implemented in any order.

  
  //MARK: Router
protocol FeedsRouterInput {
    func navigateToSomewhere()
}


class FeedsRouter: FeedsRouterInput {
    weak var viewController: FeedsViewController!
    
    // MARK: Navigation
    func navigateToSomewhere() {
        // NOTE: Teach the router how to navigate to another scene. Some examples follow:
        
        // 1. Trigger a storyboard segue
        // viewController.performSegueWithIdentifier("ShowSomewhereScene", sender: nil)
        
        // 2. Present another view controller programmatically
        // viewController.presentViewController(someWhereViewController, animated: true, completion: nil)
        
        // 3. Ask the navigation controller to push another view controller onto the stack
        // viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
        
        // 4. Present a view controller from a different storyboard
        // let storyboard = UIStoryboard(name: "OtherThanMain", bundle: nil)
        // let someWhereViewController = storyboard.instantiateInitialViewController() as! SomeWhereViewController
        // viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
    }
    
    // MARK: Communication
    func passDataToNextScene(_ segue: UIStoryboardSegue) {
        // NOTE: Teach the router which scenes it can communicate with
        
        if segue.identifier == "ShowSomewhereScene" {
            passDataToSomewhereScene(segue)
        }
    }
    
    func passDataToSomewhereScene(_ segue: UIStoryboardSegue) {
        // NOTE: Teach the router how to pass data to the next scene
        // let someWhereViewController = segue.destinationViewController as! SomeWhereViewController
        // someWhereViewController.output.name = viewController.output.name
    }
}

  

Single Responsibility Principle

The baseline of every great code has remained essentially that it be maintainable. This is where the Single Responsibility Principle enters. This states that every module, class or function should have responsibility for a single part of the functionality being provided by the software.

VIPER fulfills the single responsibility principle with the separation that it creates between various classes thus providing the developer base to write good maintainable code to keep up with constantly evolving the iOS platform.

Benefits

Testability

The loosely coupled modules ensure that testing is carried out easily and separately. Apart from the modules themselves even the objects inside are very clearly segregated and ensure great Unit testing.

Collaboration Friendly

Independent modules make VIPER great for large teams. Fewer merge conflicts, better testability and easier change of modules gives the freedom to create the initial architecture skeleton first and then passing it to other developers to implement logic.

Easy to iterate on

The codebase is familiar to most developers. Files are smaller, logic is clearer and the overall stability and flexibility are higher.

VIPER is a step forward to ensure that the SOLID code principles can be further applied to iOS apps as well. The beauty of this architecture is that it stays true to the theory behind its implementation. The only issue would be as VIPER is a non-standard architecture for the iOS, the developer has to at times go against the native API's standard usage. But then again, as long as the pros outweigh the cons, jumping on the VIPER bandwagon seems like a good idea.


Blogger-Picture
Prakunj Chaudhary
Software Developer