The VIPER-Templates Wiki has tons of information to get you started.
VIPER is a 6 tier architecture that abstracts module tasks into each tier such that everything has a single responsibility. Its conforms to SOLID design principles and is an implementation of Clean Architecture concepts.
These templates are written in Swift for use with Xcode. Current supported versions of Swift are:
- Swift 4
- Swift 3
- Swift 2.3
Head over to the VIPER-Templates Wiki for some in-depth information on VIPER and using these templates.
Did I mention there is a VIPER-Templates Wiki, you should check it out!
Note: Even though the wiki is linked three times above, the wiki is all displayed below because sometimes developers are lazy.
- Keep it sexy
- Write "Creating a VIPER Stack"
- Write script to generate README from wiki pages (currently manual process)
- VIPER - An overview of VIPER
- (NOT IMPLEMENTED) Creating a VIPER Stack - step by step process of creating an example VIPER stack
- The VIPER Layers Explained
- Installing - how to install the templates in less than a minute
- Installation Troubleshooting - a few possible problems and solutions with installing the templates
- Updating - how to update with new changes to the templates
- Using the Templates - how to use the templates to create your VIPER stacks
- Organizing the Stack - one way of organizing the VIPER files in an Xcode project
- What are These? - a general description of the files created by the templates
- Mocking Made Easy - short guide showing you how to use the mock files for testing
- Alternative Resources - some other perspectives and implementations of VIPER
iOS is still a very young technology and as you may have found out an MVC architecture is anything but useful for long running applications. The fuboTV iOS application is adopting the VIPER architecture throughout.
VIPER is a 6 tier architecture that abstracts module tasks into each tier such that everything has a single responsibility. Its conforms to SOLID design principles and is an implementation of Clean Architecture concepts.
Main Goals Of VIPER:
- Make code easy to iterate on
- Make projects collaboration-friendly
- Create reusable modules with separated concerns
- Make code easy to test
A very simplistic representation would be:
- Wireframe (Router) - instantiation and navigation
- Presenter - business logic
- Interactor - data logic
- View - user interface
- Service - retrieves entities
- Entity - data object
Most connections are two way and each direction is abstracted into an interface. For example:
- Wireframe talks to the Presenter through the WireframeToPresenterInterface
- Presenter talks to the Wireframe through the PresenterToWireframeInterface
The abstracted interfaces provide us with a way to easily conform to the interface (Goal 2 & 3). This lets us easily replace objects by creating new objects that only need to implement the interfaces (Goal 1 and 3). This also lets us create mock objects that can easily be injected for testing (Goal 4).
VIPER is not the end all of architectures. It solves many problems that arise from MVC, but sometimes the technology of our IDE's doesn't mix well (ex: storyboards and segues). If you see a way to improve VIPER, please be vocal, we want it to improve!
The wireframe is responsible for instantiation and navigation. It is the interface into the VIPER stack. When creating a VIPER stack, you instantiate the wireframe and it instantiates all the other layers and connects them properly. A wireframe constructor could look like this:
lazy var moduleInteractor = Interactor()
lazy var modulePresenter = Presenter()
lazy var moduleView = View()
init() {
super.init()
let i = moduleInteractor
let p = modulePresenter
let v = moduleView
i.presenter = p
p.interactor = i
p.view = v
p.wireframe = self
v.presenter = p
}
The wireframe maintains strong references to module layers so they do not get deallocated. It initializes all of them, and connects each one to the other, correctly.
With navigation, its specifically navigation to the stack or away from the stack. Lets say you want to display a login stack from the home stack of the application.
//HomeWireframe.swift
lazy loginModule: Login = LoginWireframe()
func presentLogin() {
loginModule.present(onViewController: moduleView)
}
//LoginWireframe.swift
func present(onViewController viewController: UIViewController) {
viewController.present(moduleView, animated: true)
// Here, you could also notify the presenter that the stack
// began presenting, but for login, there is no initial setup
// for this to be needed since the text fields will be empty
// presenter.beganPresenting()
}
The home wireframe has been told to present the login stack (from the HomePresenter
). This function calls the login's wireframe, which implements the module's interface protocol, presentation method. The login's wireframe implements the presentation method by telling the view controller that is passed in to present the login module's view controller.
The Presenter
is where business logic lives. It is what drives all the other layers, making decisions based on events that happen in the other layers. Think of the Presenter
like a manager, it knows what needs to happen to get certain task done, it knows who is best to do the job, and it tells those in its employ to do them, but doesn`t do any of the heavy lifting itself.
It has outlets to the other components of the VIPER stack, something like this:
weak var delegate: Delegate?
weak var interactor: PresenterToInteractorInterface!
weak var view: PresenterToViewInterface!
weak var wireframe: PresenterToWireframeInterface!
var moduleWireframe: Login {
get {
return self.wireframe as! Login
}
}
Lets take a look at a typical flow. Lets say your user wants to login to the application, so they have entered their username and password in the View
, and now they tap the Login
button.
//View.swift
@IBAction func loginTapped(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
presenter.userTappedLogin(withUsername: username, andPassword: password)
}
//Presenter.swift
func userTappedLogin(withUsername username: String, andPassword password: String) {
interactor.login(withUsername: username, andPassword: password)
}
The View will tell the Presenter
of the user event, and pass the related information. When the Presenter
gets it, it will tell the Interactor that it needs to call a service to login the user with the username and password the user entered.
Lets say the call to the login service succeeded, and the module now needs to tell the Delegate
the user has been logged in.
//Interactor.swift
func loggedIn(withUser user: User) {
presenter.loginSucceeded()
}
//Presenter.swift
func loginSucceeded() {
delegate?.loggedIn(login: moduleWireframe)
}
The Interactor will tell the Presenter
of the success, and the presenter decides to tell the Delegate
that login succeeded.
What if the login failed? Maybe the username doesn't exist, or the password was incorrect.
//Interactor.swift
func failedLogin(withError error: Error) {
presenter.loginFailed(withError: error)
}
//Presenter.swift
func loginFailed(withError error: Error) {
view.displayLoginError(withDescription: error.description)
}
The Interactor will tell the Presenter
that login failed, and pass the error along. The presenter decides to tell the View to display a login error with the description received from backend. The View can then decide how it displays said error, maybe with an alert, or just a label, what ever it wants to do.
Maybe the user forgot their password and the reset password module needs to be presented.
//View.swift
@IBAction func resetPasswordTapped(sender: AnyObject) {
presenter.userTappedResetPassword()
}
//Presenter.swift
func userTappedResetPassword() {
wireframe.presentResetPassword()
}
Here, the user event is reported from the View to the Presenter
, since there is navigation away from the login stack, to the reset password stack, the Wireframe needs to be notified. The Presenter
tells the Wireframe to present that module, however it needs to.
An View
is responsible for the user interface. It is the layer that retrieves information and events from the user and relates that to the Presenter.
It has outlets only to the Presenter of the VIPER stack, something like this:
weak var presenter: ViewToPresenterInterface!
It is important to understand that the View
is dumb, it does not drive interactions of any kind. This is typically a very difficult concept for people new to VIPER, as with MVC, we are used to responding to View
events like viewDidLoad
or viewDidAppear
. In VIPER, these events are handled by the Presenter, and the Presenter is what tells the View
what to do.
The View
in a VIPER stack is reactive, not proactive. It only updates the UI in response to a command from the Presenter. This is important to understand as this is what causes the UI to be independent of data flow and easily changed. Lets say there is a jogging application, and the module has been told to present a screen that shows all the users jogging sessions. Somehow, the Presenter is told that some jogs were fetched.
//Presenter.swift
func fetchedJogs(jogs: [Jog]) {
view.display(jogs: jogs)
}
//View.swift
func display(jogs newJogs: [Jog]) {
jogs = newJogs
tableView.reloadData()
}
When the Presenter receives jogs in someway, it then knows it needs to tell the View
to display them, so it calls the display(jogs:)
method on the View
. This particular View
uses a UITableView
to display the jogs, so it just saves the jogs and tells the tableView
to reload its data.
What if you wanted to change this implementation to use a UICollectionView
? The display(jogs:)
function would stay the same, and the Presenter/Interactor/Wireframe would never need to be touched. You could create a new View
object that conforms to the same PresenterToViewInterface
, but this one uses a UICollectionView
implementation. Then this new View
is just dropped into the place and you're all done!
A big key of the VIPER architecture is being able to easily change layers without them affecting others. So what if we changed the Jog
object to something like a Run
object? Consequently, we would need to change all the layers of the VIPER stack to use this new Run
object interface. What would be a better way?
We could create a data object that is specifically for this View
layer that has only the fields we require to display. Lets say this View
only needs to display the distance, date, and time of the Jog
.
//Jog.swift
class Jog {
var date: Date?
var distance: Double?
var location: Location?
var time: Int?
var user: User?
}
//ViewObject.swift
class ViewObject {
var date: Date?
var distance: Double?
var time: Int?
// Initializers
init(fromJog jog: Jog) {
date = jog.date
distance = jog.distance
time = jog.time
}
}
//View.swift
func display(viewObjects newViewObjects: [ViewObject]) {
viewObjects = newViewObjects
tableView.reloadData()
}
Easy enough, right? Ok, now the backend starts returning Run
objects. All we need to do is make the ViewObject
have an init(fromRun:)
//Run.swift
class Run {
var date: Date?
var distance: Double?
var endTime: Date?
var location: Location?
var startTime: Date?
var user: User?
}
//ViewObject.swift
class ViewObject {
var date: Date?
var distance: Double?
var time: Int?
// Initializers
init(fromJog jog: Jog) {
// fromJog implementation
}
init(fromRun run: Run) {
date = run.date
distance = run.distance
time = run.endTime - run.startTime
}
}
All done! The View
can keep using the same ViewObject
to display the UI, and nothing needs to be changed on the View
layer to handle this new data type.
Ok, now lets say your user wants to login to the application, so the View
is displaying two text fields, one for username entry, and the other for password. The user types in their username and password, then presses a Login
button.
//View.swift
@IBAction func loginTapped(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
presenter.userTappedLogin(withUsername: username, andPassword: password)
}
//Presenter.swift
func userTappedLogin(withUsername username: String, andPassword password: String) {
interactor.login(withUsername: username, andPassword: password)
}
Here, the View
tells the Presenter of the user event, and communicates the information that it gathered (username and password). The Presenter then decides what to do with the user event. Notice this flow isn't initiating a login call to the backend. It is just notifying the Presenter of the user event.
An Interactor
is responsible for data logic. It is the layer that retrieves the data from any source it needs to. For instance, it can communicate with a Service or a local data store such as CoreData.
It has outlets only to the Presenter of the VIPER stack, something like this:
weak var presenter: InteractorToPresenterInterface!
Lets take a look at a typical flow. Lets say your user wants to login to the application, so the presenter has been told the user wants to login with their username and password:
//Presenter.swift
func userTappedLogin(withUsername username: String, andPassword password: String) {
interactor.login(withUsername: username, andPassword: password)
}
//Interactor.swift
lazy var loginService: LoginService = LoginService()
func login(withUserName username: String, andPassword password: String) {
loginService.login(withUsername: username, andPassword: password,
success: { (user: User) in
self.loggedIn(withUser: user)
},
failure: { (error: Error) in
self.failedLogin(withError: error)
})
}
Here, the Interactor
is told to login with the username and password by the Presenter. The Interactor
knows that it needs to make a call to the loginService
to login the user and get the user
object from the web service. It can then implement the success
and failure
completion blocks as it needs to.
So the loginService
succeeded, and the success block of the Service is ran, and now this information needs to be conveyed to the Presenter.
//Interactor.swift
func loggedIn(withUser: user) {
presenter.logginSucceeded()
}
So what if instead of calling a Service, you instead want to get information from some sort of data manager like Realm? Lets say we are making a jogging application that records the user's jogging sessions.
//Presenter.swift
func beganPresenting() {
interactor.fetchJogs()
}
//Interactor.swift
func fetchJogs() {
let realm = try! Realm()
let allJogs = realm.objects(Jog.self)
presenter.fetchedJogs(Array(allJogs))
}
Notice the interface to the Interactor
from the Presenter is the same as if the Interactor
was going to call a Service. The Presenter has no idea how the Interactor
fetches jogs (or logs in the user). The Interactor
is responsible for this interaction.
A Service
is a modularized object that typically connects to a web interface to get data. This allows the connection to a web endpoint to be completely abstracted and easily changeable. The Interactor will call the Services
and handle the responses, whether it is a success or failure.
Continuing with the Login flow example, lets say the Interactor has been told to login with a username and password.
//Interactor.swift
lazy var loginService: LoginService = LoginService()
func login(withUserName username: String, andPassword password: String) {
loginService.login(withUsername: username, andPassword: password,
success: { (user: User) in
self.loggedIn(withUser: user)
},
failure: { (error: Error) in
self.failedLogin(withError: error)
})
}
//LoginService.swift
func login(withUsername username: String,
andPassword password: String,
success completion: (user: User),
failure: ((error: Error) -> Void)) {
let parameters: Parameters = [
"username": username,
"password": password,
]
let request = Alamofire.request("http://www.myserver.com/login,
method: Method.get,
parameters: parameters,
encoding: JSONEncoding.default)
request.responseJSON { (response: Response) in
switch response.result {
case .Success(let value):
let user = User(fromJson: value)
completion(user: user)
case .Failure(let error):
failure(error: error)
}
}
}
Here, we use Alamofire to handle the HTTP request to our server and login the user with username and password provided. The response is JSON, that is then parsed into a User
object by the User
entities init from JSON method. It is then returned to the completion handler block.
Similarly, if there was an error (maybe the username or password was incorrect), the failure block is called with the Error
that was received.
So, with this implementation, notice that we can easily swap out Alamofire for any other networking API. The Interactor will still call the login service the same way, and receive the User
or Error
object through the same handler blocks, allowing this implementation to change on the fly without affecting any other part of our application!
Entities
are data objects that we use throughout the application. They can be used anywhere, and are typically created by Services. They can be passed around any of the VIPER layers and used as needed.
Lets take a look a typical User
entity
//User.swift
class User {
// Identifier
let userId: Int
// Instance Variables
let gender: String?
let password: String?
let username: String?
// Initializers
init(withUserId newUserId: Int) {
userId = newUserId
}
init?(fromJson json: [String: AnyObject]) {
let json = JSON(jsonDictionary)
let identifier = json["userId"].int
guard identifier != nil else {
return nil
}
self.init(withUserId: identifier)
gender = json["gender"].string
password = json["password"].string
username = json["username"].string
}
}
Here, we have a basic User
object. In the init(withJson:)
method we use SwiftyJSON to easily parse the JSON's values into the objects instance variables.
Here is a short video showing you how to install these templates:
- Clone the VIPERTemplates repository
- Run the install script (double click)
If you do not see the templates in Xcode, head over to Installation Troubleshooting, and see if there is a solution.
The installation of the VIPER templates is meant to be as easy and seamless as possible. The install script creates a symbolic link in the Xcode directory to the templates. This allows them to integrate directly into Xcode and be easily used through the File->New File
Xcode flow. However, there is a problem that can arise based on the assumption of the install script.
One very common occurrence is to have multiple versions of Xcode. You could have Xcode 7 (Swift 2.2), Xcode 8.2 (Swift 2.3), and Xcode 8.3 (Latest).
If your Xcode is not installed in the default /Applications/Xcode
location, then you will need to change this path. It is very easy though!
The below examples use an applications directory with multiple Xcodes that looks like this:
- Double Click the install script.
- When the script asks for the path to Xcode, instead of pressing return, put in the correct path.
Alternatively, you can input the path to your Xcode.app as a command line argument
The VIPER Templates are always being improved and updates are pushed fairly frequently. The update process has had considerable thought put into it, and is meant to be just as easy, if not easier, than using the templates themselves. So what are the possible scenarios for updating?
Because Xcode is an app from the App Store, it is updated via the App Store, and the template directory does not have a static location on a users computer. So when Xcode updates, the template directory gets overwritten with Xcode's version of the templates. Getting the VIPER Templates back is very easy though!
- Run the install script
- All done!
Simply running the install script again re-adds the links to the VIPER templates into Xcode templates directory.
Since the templates are constantly being updated and improved, you will want the latest and greatest. So how do you update?
- Open Terminal
- cd into the VIPERTemplates repository
git pull
- All done!
Since the install script creates symbolic links to the templates files, changing the files will instantly reflect in Xcode. So updating the repository with a pull
from Git will update all your templates instantly as well!
Once the templates are installed, using them is very easy. Here is a short video showing you how:
- Open an Xcode project
- Create a new file (File > New > File or ⌘N)
- Choose VIPER
- Set your VIPER stack base name
- Continue adding files as regular to your app target
- Repeat steps 4->8 for VIPER Test, adding the files to your test target
- Continue to [Organizing the Stack](../../wiki/Organizing the Stack)
Once all the files for the stack are created, organize them so they are easy to work with. Here is a short video showing you one way of how:
- You want to organize the files into their respective layers for easy access. These layers are typically referred to as:
- Data Logic - the Interactor and any associated tests or mocks.
- Routing - the Wireframe and Presenter, as well as any associated tests or mocks.
- User Interface - the View, storyboard, view objects, and any associated tests or mocks.
- Try to keep your tests files as close to the implementation files as possible.
- This is so when you see the implementation file you also see the test file, and you don't skip out on tests (something that is easy to do when you're focused on implementation).
These templates create fully configured, and tested, base VIPER stacks in either Swift 2.3 or Swift 3 -> Swift 4. There is also the ability to choose between XCTest and Quick & Nimble unit tests.
The VIPER Templates create a large amount of files for just two templates. Its probably something you aren't used too, and can be a bit overwhelming if you're just getting into VIPER. However, understand that a lot of time, effort, and thought has gone into each of these files and their structure.
Below is the list of files created by the templates. The filenames were created such that they should be self explanatory of the intent of the file. However, some context is added to files that may not be instantly understood. If there is anything that is confusing, please let me know and I will do my best to clarify.
Interactor
InteractorProtocols
- Presenter->Interactor protocol
Presenter
PresenterProtocols
- Delegate, Interactor->Presenter, View->Presenter, Wireframe->Presenter protocols
View
ViewProtocols
- Navigation and Presenter->View protocols
Wireframe
WireframeProtocols
- Module and Presenter->Wireframe protocols
DelegateMock
- mocked interface for the stacks delegate
InteractorTests
InteractorToPresenterInterfaceMock
PresenterTests
PresenterToInteractorInterfaceMock
PresenterToViewInterfaceMock
ViewTests
ViewToPresenterInterfaceMock
WireframeInterfacesMock
- Mocks both the module interface and the PresenterToWireframeInterface since these objects need to be the same internally
WireframeTests
WireframeToPresenterInterfaceMock
One of the core goals of VIPER is to make unit testing easy. Many times developers are introduced to unit testing and end up hating it because it can be difficult. But why is it difficult? More often then not, the code was built without the thought of testing in mind. In other words, it wasn't written to be tested.
VIPER, by default, is built to be tested by abstracting the interfaces into protocols. This allows anything to conform to the protocol and thus the layers are easily injectable for unit testing.
Say we need to write code to capture a user tapping login. The user story would be something like this:
Given I am a user trying to login
When I tap the login button
Then I should login with my username and password
The tap on the button needs to cause the Presenter to be told that the user is trying to login in using the inputted username and password. Inside the View layer, the variable presenter
is anything that conforms to the ViewToPresenterInterface
. So what do we do if we want to test that, when the user taps login, the Presenter is told of the user event? The templates already abstract out the ViewToPresenterInterfaceMock
. So in TDD we would start by writing our failing test, something like this:
//ViewTests.swift
@testable import Project
class ViewTests: XCTestCase {
var view: JogsView!
var presenterMock: JogsViewToPresenterInterfaceMock!
override func setUp() {
super.setUp()
let sb = UIStoryboard(name: Jogs.storyboardIdentifier, bundle: Bundle(for: JogsView.self))
view = sb.instantiateViewController(withIdentifier: .viewIdentifier) as! JogsView
presenterMock = JogsViewToPresenterInterfaceMock()
_ = view.view
// This is where we easily inject a mocked interface into the view for testing
view.presenter = presenterMock
}
func testLoginWasTappedWithUsernameAndPasswordShouldTellPresenterUserTappedLogin() {
// Arrange
view.usernameField.text = "testUserName"
view.passwordField.text = "testPassword"
// Act
view.loginTapped(view.loginButton)
// Assert
XCTAssert(presenterMock.functionsCalled.contains("userTappedLogin(withUsername:andPassword:)"))
XCTAssertEqual(presenterMock.withUsername, "testUserName")
XCTAssertEqual(presenterMock.andPassword, "testPassword")
}
}
Now we can run this test and see that it fails because we haven't implemented anything yet. Next, we define the interface function to call for the Presenter.
//PresenterProtocols.swift
protocol ViewToPresenterInterface {
func userTappedLogin(withUsername username: String, andPassword password: String)
}
Pretty good, next we need to have our actual Presenter and our ViewToPresenterInterfaceMock
conform to the protocols.
//Presenter.swift
class Presenter {
weak var interactor: PresenterToInteractorInterface!
weak var view: PresenterToViewInterface!
weak var wireframe: PresenterToWireframeInterface!
}
extension Presenter: ViewToPresenterInterface {
func userTappedLogin(withUsername username: String, andPassword password: String) {
interactor.login(withUsername: username, andPassword: password)
}
}
For the mocked interface, we just need to update the extension to conform to protocol, and save the input values so we can test them. Our mock turns into something like this:
//ViewToPresenterInterfaceMock.swift
class ViewToPresenterInterfaceMock {
var functionsCalled = [String]()
var andPassword: String?
var withUsername: String?
}
extension ViewToPresenterInterfaceMock: ViewToPresenterInterface {
func userTappedLogin(withUsername username: String, andPassword password: String) {
functionsCalled.append(#function)
withUsername = userName
andPassword = password
}
}
Great, everything conforms to the protocols we need them to, the View tests are injecting the mocked ViewToPresenterInterface
, and everything compiles. However, we still have our failing test because we haven't implemented the functionality yet.
//View.swift
class View {
weak var presenter: ViewToPresenterInterface!
@IBAction func loginTapped(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
presenter.userTappedLogin(withUsername: username, andPassword: username)
}
}
Now we are at a spot where we can run our tests and see if the presenter is told of the user event with the correct values being passed in. What happens when we run the tests?
func testLoginWasTappedWithUsernameAndPasswordShouldTellPresenterUserTappedLogin() {
// Arrange
view.usernameField.text = "testUserName"
view.passwordField.text = "testPassword"
// Act
view.loginTapped(view.loginButton)
// Assert
XCTAssert(presenterMock.functionsCalled.contains("userTappedLogin(withUsername:andPassword:)"))
XCTAssertEqual(presenterMock.withUsername, "testUserName")
XCTAssertEqual(presenterMock.andPassword, "testPassword")
// TEST FAILED: "testUserName" != "testPassword"
}
Oh dear, how could our test have failed?? But the message makes sense and we can easily find the error
//View.swift
class View {
weak var presenter: ViewToPresenterInterface!
@IBAction func loginTapped(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
presenter.userTappedLogin(withUsername: username, andPassword: username)
// We passed the username as the password on accident!
}
}
A quick change to the presenter
call and we are good to go!
presenter.userTappedLogin(withUsername: username, andPassword: password)
Run the unit tests again and they should pass without problems.
VIPER isn't an industry standard. Just as there is no one way to implement MVC or MVVM, there are many different flavors of VIPER. The templates version has very verbose names to reduce the vocabulary and make it as easy as possible to get into VIPER. However, seeing others implementations is very useful.
Differences:
- Uses original vocabulary. Makes it very difficult to start with VIPER as you have to now all the vocabulary to get a decent understanding of what is happening. For example: their
View.eventHandler
= ourView.presenter
- Brigade has an additional
DataManager
layer. In practice this ends up turning the Interactor into a pass through layer. In our version, the Interactor interacts withServices
directly.
Differences:
- Uses original vocabulary. Makes it very difficult to start with VIPER as you have to now all the vocabulary to get a decent understanding of what is happening. For example: their
View.eventHandler
= ourView.presenter
- Examples are Objective-C. This ends up with them using PONSOs instead of base objects, but their intent is the same:
Have very simple data structures to represent
Entities
This post is more of an exploration of many different architectures. It has decent break downs of each with explanations. Very useful to get some exposure to lots of different architectures.