Insights

Understanding the ins and ours of Viper

VIPER is complex, scary, and well - different. There’s a hint of venom beneath the surface of VIPER and it can hurt. Yet, over time the pain subsides, and it becomes one of the cures to structuring your code. Bye bye venom. Hello happiness.

Here's what iOS developers can relate to:

Page 1-640x360

The VIPER design pattern is a source of confusion & misunderstanding for many developers. The pros & cons and practical usage of this pattern are unclear. After working on applications with the MVC, MVVM, MVVM + RxSwift patterns, I wanted to gain an in-depth understanding of this vicious-sounding design pattern and see what the buzz (hiss?) was all about. As a result, I decided to test the following hypothesis:

As a developer I would like to use the VIPER design pattern to build reusable modules.

We wrote this post hoping that it empowers you to evaluate whether or not VIPER is the right option for your project or framework.

VIPER

A few folks over at Mutual Mobile wanted to improve their testing coverage on their iOS apps. As a result, they came up with the pattern itself. More in regards to where it came from can be found here.

Literally speaking, Viper is a backronym standing for View, Interactor, Presenter, Entity, Router.

Theoretically speaking, while using Viper you application or framework would be broken down into VIPER “modules” - reusable & module components. In diagram form it translates into this:

Page 3-640x360

View: The interface layer lays out the view, updates images and labels, passes user input to the presenter

Interactor: Business logic, manipulation of data, transformation of models, interacts with API Services & data manager classes. Passes output to the presenter

Presenter: Formats data received from the interactor for the view, receives input from view and passes it to the interactor

Entity: A simple data representing what is needed for the VIPER Module to function

Router: Responsible for module navigation and application navigation

VIPER in code does not work in the same sequence as the letters you read. In other words… the actions in your code will flow more so in the order below:

Interactor → Router , Entity, & Externals → Presenter → View

VIPER becomes more like IREPV… That is a word to someone, somewhere.

EXPERIMENT

We validated the hypothesis of using VIPER in a reusable module by building a framework. The framework is focused on playing video content. Playing video involves UI updates, data downloading & data synchronization. These interactions proved that a video playback module would be a worthwhile candidate for a VIPER structured module.

OVERVIEW

VIPER modules are protocol-oriented. Every function, property, input, and output is implemented via a protocol.

As a result we used the idea of a:

Contract: a swift file describing all protocols used in the VIPER module. It is similar to an objective-C header file. Code snippet below:

//*** Interactor//*//*protocol VPKVideoPlaybackInteractorProtocol: class {

init(entity: VPKVideoType)}

protocol VPKVideoPlaybackInteractorInputProtocol: class {

var presenter: VPKVideoPlaybackInteractorOutputProtocol? { get set }var videoType: VPKVideoType { get set }var remoteImageURL: URL? { get set }var localImageName: String? { get set }

var playbackManager: (VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol)? { get set }

// PRESENTER -> INTERACTORfunc didTapVideo(videoURL: URL, at indexPath: NSIndexPath?)func didScrubTo(_ timeInSeconds: TimeInterval)func didSkipBack(_ seconds: Float)func didSkipForward(_ seconds: Float)func didToggleViewExpansion()func didMoveOffScreen()func didReuseInCell()}

We also wanted developers of this framework to have an easy way to configure VIPER modules.

As a result we used the idea of a:

Builder: A simple object used to configure the VIPER module and set up dependencies. It instantiates the presenter and interactor. Ultimately it returns the view to the viewcontroller.

public class VPKVideoPlaybackBuilder: VPKVideoPlaybackBuilderProtocol {

//Feedpublic static func vpk_buildViewInCell(for videoType:VPKVideoType, at indexPath: NSIndexPath, with playbackBarTheme:ToolBarTheme = .normal, completion viewCompletion:VideoViewClosure) {

let interactor = VPKVideoPlaybackInteractor(entity:videoType)let presenter: VPKVideoPlaybackPresenterProtocol &VPKVideoPlaybackInteractorOutputProtocol =VPKVideoPlaybackPresenter(with: false, showInCell: nil,playbackTheme: playbackBarTheme)viewCompletion(VPKDependencyManager.videoView(with:interactor, and: presenter))

}}

We also used a protocol to set up all dependencies for the module:

class VPKDependencyManager: VPKDependencyManagerProtocol {

static func videoView(with interactor:VPKVideoPlaybackInteractorProtocol &VPKVideoPlaybackInteractorInputProtocol, and presenter:VPKVideoPlaybackPresenterProtocol &VPKVideoPlaybackInteractorOutputProtocol) -> VPKVideoView {

let videoView = VPKVideoView(frame: .zero)let playbackBarView: VPKPlaybackControlViewProtocol =VPKPlaybackControlView(theme: presenter.playbackTheme ?? .normal)let videoPlaybackManager: VPKVideoPlaybackManagerInputProtocol &VPKVideoPlaybackManagerOutputProtocol &VPKVideoPlaybackManagerProtocol = VPKVideoPlaybackManager.shared//Dependency setuppresenter.interactor = interactorinteractor.presenter = presentervideoView.presenter = presentervideoView.playbackBarView = playbackBarViewpresenter.playbackBarView = playbackBarViewplaybackBarView.presenter = presenterpresenter.videoView = videoViewinteractor.playbackManager = videoPlaybackManagerreturn videoView

}}

THE FRAMEWORK: VIDEOPLAYBACKKIT

Page 7-300x98

Let’s consider the user story:

**PLAY A VIDEO **

Using AVFOUNDATION we know the following:

  • Must use AVPlayer
  • AVPlayer will give you an AVPlayerLayer to add onto your view
  • AVPlayer takes time to prepare - particularly with remote video urls
  • Remote video data needs time to download
  • Two primary view actions
    • Play
    • Pause
  • Results of these actions:
    • AVPlayer receives video url to download data for
    • When ready, the AVPlayer returns an AVPlayerLayer
    • The AVPlayerLayer must be added to a view
    • On pause, AVPlayer must stop playback

**IREPV (AKA VIPER) & AVKit, AVFoundation **

Page 8-640x360 

INPUTS AND OUTPUTS

Now this is key to understanding the pattern, calming your brain down, avoiding the fall into rabbithole complexities.

Major Lesson 1: Understand the inputs and the outputs of your module.

View

  • Input: didTapPlay
  • Output: Presenter didTapPlay

Presenter

  • Input: didTapPlay receives from view
  • Output: onPlayerLayerSuccess(playerLayer) - outputs this to the presenter

Interactor

  • Input: didTapPlay - tells the external manager

  • Output: onVideoPlayerLayerSuccess - passes player layer to the presenter

    protocol VPKVideoPlaybackInteractorOutputProtocol: class {

var progressTime: TimeInterval { get set }

//INTERACTOR --> PRESENTERfunc onVideoResetPresentation()func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer)func onVideoLoadFail(_ error: String)func onVideoDidStartPlayingWith(_ duration: TimeInterval)func onVideoDidStopPlaying()func onVideoDidPlayToEnd()func onVideoPlayingFor(_ seconds: TimeInterval)}

Once the view receives the AVPlayerLayer from the presenter, it can reload its interface.

protocol VPKVideoViewProtocol: class {func reloadInterface(with playerLayer: AVPlayerLayer)}

INTERACTOR AND PRESENTER RELATIONSHIP

The relationships amongst the pieces of VIPER are different from those of other iOS patterns. Each object holds onto each other (weakly of course). You can think of it as one being the delegate for the other. See the example below:

  1. The Presenter holds a weak reference to the interactor. The presenter ONLY cares about the interactor’s input protocol. The forwarding of the “didTapPlay” input.

    protocol VPKVideoPlaybackPresenterProtocol: class {

var interactor: VPKVideoPlaybackInteractorInputProtocol? { get set }//weak}

  1. The Presenter forwards the input message; however, the Presenter also depends on the interactors knowledge of the video entity data.

    public class VPKVideoPlaybackPresenter:VPKVideoPlaybackPresenterProtocol {func didTapVideoView() {guard let safeVideoUrl = interactor?.videoType.videoUrl else {return }interactor?.didTapVideo(videoURL: safeVideoUrl, at:self.indexPath)

}}

PROTOCOL CONFORMANCE

The presenter and interactor relationship is the most important part of the data flow.

The Presenter cares about the Interactors Output

The Interactor cares about the Presenters Input

See below:

The presenter conforms to the interactor’s output protocol.

//MARK: INTERACTOR --> Presenterextension VPKVideoPlaybackPresenter:VPKVideoPlaybackInteractorOutputProtocol { func onVideoLoadSuccess(_ playerLayer: AVPlayerLayer) {videoView?.reloadInterface(with: playerLayer)

}}

The Interactor uses the weak property of the presenter to pass back the output data.

public class VPKVideoPlaybackInteractor:VPKVideoPlaybackInteractorProtocol {

weak var presenter: VPKVideoPlaybackInteractorOutputProtocol?

 func playbackManager(_: VPKVideoPlaybackManager, didPrepareplayerLayer: AVPlayerLayer) {presenter?.onVideoLoadSuccess(playerLayer)

}}

Page 11 Diagram-640x360

THE PROS

Pros Page 11-640x360

THE CONS

Cons Page 12-640x360

RECOMMENDATION

We recommend using a form of VIPER if:

  • Your team is open to learning a new pattern
  • Your team has at least 1 week to learn, understand, and experiment with it
  • Easier to support a test driven approach due to the pattern’s decoupled nature
  • You are excited about it :)
  • Team members are responsible for different modules

We do not recommend using a form of VIPER if:

  • Your team is reactive, and features change on a nearly daily basis
  • You are building a small application that is an experiment and being used to test a problem
  • You do not care for tests

VIPER will shine with long term projects, applications where security and business logic are high, and when testing must be implemented.

You will notice your code become cleaner. You will be able to separate features & modules amongst team members easily. When there is a UI formatting bug you will instantly know to check the Presenter class. When there is a logical bug, you know it’s stemming from the interactor. More clarity, less venom.

ADDITIONAL INFO

This topic was presented at try! Swift NYC 2017. The presentation can be found here.

All additional try! Swift talks can be found here.