神刀安全网

iOS Project Architecture: Using VIPER

When developing an iOS app, it’s important to think about what iOS project architecture you should use. Most developers use the pattern suggested by Apple : the so-called MVC (Model-View-Controller) architecture. However, as well-established as it is, the MVC has its flaws.  For one, because of its simplicity, it leads even the most experienced engineers to put any code that doesn’t belong to a View nor to a Model in the Controller’s logic – generating huge chunks of code in the controller and really compact views and models.

In this post, we’ll present VIPER, one of the trending alternatives to MVC that might help you overcome its limitations while keeping your code modular and well-organized, improving your development process.

1. What is VIPER?

VIPER is a backronym for View, Interactor, Presenter, Entity and Router. It’s basically an approach that implements the Single Responsibility Principle to create a cleaner and more modular structure for your iOS project. The ideia behind this pattern is to isolate your app’s dependencies, balancing the delegation of responsibilities among the entities. This is achieved by using the the following architecture:

iOS Project Architecture: Using VIPER

The diagram above illustrates the VIPER architecture, in which each block corresponds to an object with specific tasks, inputs and outputs. Think of these blocks as workers in an assembly line: once the worker completes its job on an object, the object is passed along to the next worker, until the product is finished.

The connections between the blocks represent the relationship between the objects, and what kind of information they transmit to each other. The communication from one entity to another is given through protocols, which we’ll explain further in this post.

2. iOS Project Architecture

Having in mind the true purpose of the VIPER architecture, it’s now important to understand a bit more about each part, and what their responsibilities are. To do so, we’ll develop a basic application (code also available on GitHub ) that fetches a list of articles from a REST API and displays them in the user’s screen.

2.1. View

The VIPER View in an iOS application is a UIViewController that contains a sub view, which can be either implemented programmatically or using the interfacer builder (IB). Its sole responsibility is to display what the Presenter tells it to, and handle the user interactions with the screen. When the user triggers any event that requires processing, the View simply delegates it to the Presenter and awaits for a response telling it what should be displayed next.

This is how the View for our Article Visualization app would look like in Swift:

 /*  * Protocol that defines the view input methods.  */ protocol ArticlesViewInterface: class {     func showArticlesData(articles: [Article])     func showNoContentScreen() }  /*  * A view responsible for displaying a list  * of articles fetched from some source.  */ class ArticlesViewController : UIViewController, ArticlesViewInterface {     // Reference to the Presenter's interface.     var presenter: ArticlesModuleInterface!      /*      * Once the view is loaded, it sends a command      * to the presenter asking it to update the UI.      */     override func viewDidLoad() {         super.viewDidLoad()         self.presenter.updateView()     }      // MARK: ArticlesViewInterface      func showArticlesData(articles: [Article]) {         self.articles = articles         self.tableView.reloadData()     }      func showNoContentScreen() {         // Show custom empty screen.     } } 

2.2. Presenter

The Presenter works like a bridge between the main parts of a VIPER module. On one way, it receives input events coming from the View and reacts to them by requesting data to the Interactor. On the other way, it receives the data structures coming from the Interactor , applies view logic over this data to prepare the content, and finally tells the View what to display.

Here’s an example of a Presenter for our Article Visualization app:

 /*  * Protocol that defines the commands sent from the View to the Presenter.  */ protocol ArticlesModuleInterface: class {     func updateView()     func showDetailsForArticle(article: Article) }   /*  * Protocol that defines the commands sent from the Interactor to the Presenter.  */ protocol ArticlesInteractorOutput: class {     func articlesFetched(articles: [Article]) }   /*  * The Presenter is also responsible for connecting  * the other objects inside a VIPER module.  */ class ArticlesPresenter : ArticlesModuleInterface, ArticlesInteractorOutput {     // Reference to the View (weak to avoid retain cycle).     weak var view: ArticlesViewInterface!      // Reference to the Interactor's interface.     var interactor: ArticlesInteractorInput!      // Reference to the Router     var wireframe: ArticlesWireframe!       // MARK: ArticlesModuleInterface      func updateView() {         self.interactor.fetchArticles()     }      func showDetailsForArticle(article: Article) {         self.wireframe.presentDetailsInterfaceForArticle(article)     }      // MARK: ArticlesInteractorOutput      func articlesFetched(articles: [Article]) {         if articles.count > 0 {             self.articles = articles             self.view.showArticlesData(articles)         } else {             self.view.showNoContentScreen()         }     } } 

2.3. Interactor

We can think about this object as a collection of use cases inside of a specific module. The Interactor contains all the business logic related to the entities and should be completely independent of the user interface (UI).

In our Article Visualization app, one use case example is to fetch the list of articles from the server. It’s the Interactor ‘s responsibility to make the requests, handle the responses and convert them to an Entity which, in this case, is an Article object.

Once the Interactor finishes running some task, it notifies the Presenter about the result obtained. One important thing to have in mind is that the data sent to the  Presenter should not implement any business logic, so the data provided by the Interactor should be clean and ready to use.

In our Article Visualization app, the Interactor  would be responsible for fetching the articles from an API:

 /*  * Protocol that defines the Interactor's use case.  */ protocol ArticlesInteractorInput: class {     func fetchArticles() }   /*  * The Interactor responsible for implementing  * the business logic of the module.  */ class ArticlesInteractor : ArticlesInteractorInput {     // Url to the desired API.     let url = "https://www.myendpoint.com"      // Reference to the Presenter's output interface.     weak var output: ArticlesInteractorOutput!       // MARK: ArticlesInteractorInput      func fetchArticles() {         Alamofire.request(.GET, url).responseArray { (response: Response) in             let articlesArray = response.result.value             self.output.articlesFetched(articlesArray!)         }     } } 

2.4. Entity

The Entity is probably the simplest element inside a VIPER structure. It encapsulates different types of data, and usually is treated as a payload among the other VIPER components. One important thing to notice is that the Entity is different from the Data Access Layer, which should be handled by the Interactor .

In our Article Visualization app, the Article class would be an example of an Entity :

 class Article {     var date: String?     var title: String?     var website: String?     var authors: String?     var content: String?     var imageUrl: String? } 

2.5. Router

The last and perhaps most peculiar element in the VIPER architecture is the Router , which is responsible for the navigation logic between modules, and how they should happen (e.g. defining an animation for presenting a screen, or how the transition between two screens should be done). It receives input commands from the Presenters to say what screen it should route to. Also, the Router should be responsible for passing data from one screen to the other.

The Router should implement a protocol that defines all the navigation possibilities for a specific module. That’s a good because it enables a quick overview of all the paths an app can take by only looking at a Router ‘s protocol.

Because of a limitation from the iOS framework, only ViewControllers can perform a transition between screens, so a Router must contain a reference to the module’s controller, or any of its children.

Here’s how our router would look like in our Article Visualization app (note that the Router is widely referred to as Wireframe ).

 /*  * Protocol that defines the possible routes from the Articles module.  */ protocol ArticlesWireframeInput {     func presentDetailsInterfaceForArticle(article: Article) }   /*  * The Router responsible for navigation between modules.  */ class ArticlesWireframe : NSObject, ArticlesWireframeInput {     // Reference to the ViewController (weak to avoid retain cycle).     weak var articlesViewController: ArticlesViewController!      // Reference to the Router of the next VIPER module.     var detailsWireframe: DetailsWireframe!       // MARK: ArticlesWireframeInput      func presentDetailsInterfaceForArticle(article: Article) {         // Create the Router for the upcoming module.         self.detailsWireframe = DetailsWireframe()          // Sends the article data to the next module's Presenter.         self.sendArticleToDetailsPresenter(self.detailsWireframe.detailsPresenter, article: article)          // Presents the next View.         self.detailsWireframe.presentArticleDetailsInterfaceFromViewController(self.articlesViewController)     }       // MARK: Private      private func sendArticleToDetailsPresenter(detailsPresenter: DetailsPresenter, article: Article) {         detailsPresenter.article = article     } } 

3. When should you use VIPER?

When creating a project that has a potential of evolving, it’s important to think of a structure that will scale well and enable many developers to simultaneously work on it as seamlessly as possible – and the MVC structure might not be enough to keep your project sufficiently organized.

It’s really common for developers to find themselves debugging a huge class, like trying to find a needle in a haystack. With the loose coupling between the objects that VIPER proposes, you’ll notice that:

  • It’s easier to track issues via crash reports (due to the Single Responsibility Principle)
  • Adding new features is easier
  • The source code will be cleaner, more compact and reusable
  • There are less conflicts with the rest of the development team
  • It’s easier to write automated tests (!), since your UI logic is separated from the business logic.

4. When should you NOT use VIPER?

As for every problem you’re trying to solve, you should recur to the tool that best suits your needs. Due to the number of elements involved, this architecture causes an overhead when starting a new project (though it largely pays off in the long run), so VIPER can be an overkill for small projects that do not intend to scale.

If the team isn’t completely aligned with maintaining the VIPER structure, you’ll end up with an MVC-VIPER mix that can cause headaches – so make sure the team is completely in sync before moving forward with VIPER.

5. Wrapping up

VIPER is a really cool iOS project architecture pattern among others, like MVP and MVVM. If you’re curious to know more about the VIPER architecture, you can check out the repository with the full implementation of the example used in this post. Feel free to contribute with issues and pull requests!

What is your favorite iOS project architecture? Please share your opinion in the comments!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » iOS Project Architecture: Using VIPER

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址