VIPER: conceitos-chave, o padrão View-Interactor-Presenter-Entity-Router

Autor: IT Sectr Publicado: 2026-02-16 Tempo de leitura: 9 min

VIPER (View-Interactor-Presenter-Entity-Router) é uma arquitetura modular desenvolvida na Mutual Mobile para aplicativos iOS. O VIPER divide o aplicativo em cinco camadas: View cuida da exibição, Interactor da lógica de negócios, Presenter da preparação de dados, Entity dos modelos de dados, Router da navegação entre módulos. O VIPER é a implementação mais detalhada do princípio de responsabilidade única entre as arquiteturas móveis. Leia mais no artigo no objc.io.

Principais pontos

  • VIPER — cinco componentes: View, Interactor, Presenter, Entity, Router com limites de responsabilidade claros
  • Modularidade — cada tela (módulo) é isolada, comunicação via protocolos
  • Router — remove a navegação do Presenter, resolvendo o problema de navegação do iOS
  • Interactor — contém a lógica de negócios e não depende do UIKit, testado com testes unitários
  • iOS-native — VIPER foi criado para UIKit antes do SwiftUI e continua sendo o padrão para grandes projetos iOS

O que é VIPER: cinco componentes da arquitetura modular

VIPER (View-Interactor-Presenter-Entity-Router) é um padrão arquitetural desenvolvido em 2013–2014 na Mutual Mobile para grandes projetos iOS. Cada tela do aplicativo é um módulo separado de cinco componentes com responsabilidades estritamente definidas. O VIPER é a implementação mais rigorosa do Princípio de Responsabilidade Única no desenvolvimento móvel: nenhum componente faz o que outro pode fazer.

View é um componente passivo responsável apenas por exibir dados passados pelo Presenter. View não contém lógica de negócios, não lida com navegação, não faz requisições de rede. No iOS — UIViewController com um ViewProtocol. Interactor é a camada de lógica de negócios que trabalha com Entity e serviços (rede, BD, GPS). Interactor não importa UIKit. Presenter é o mediador entre View e Interactor: recebe dados do Interactor, formata para exibição, passa para View. Presenter também não importa UIKit. Entity — modelos de dados (struct, class). Router — gerencia navegação: cria módulos, abre telas, passa dados entre módulos.

ComponenteResponsabilidadeDependências
ViewExibição, animações, gestosUIKit (apenas View)
InteractorLógica de negócios, rede, BDEntity, serviços
PresenterFormatação de dados, comandos ViewViewProtocol, Interactor
EntityModelos de dadosNenhuma
RouterNavegação, criação de módulosUIViewController (para transições)

Relações entre componentes são descritas por protocolos. ViewProtocol define métodos de exibição, InteractorProtocol define métodos de lógica de negócios, PresenterProtocol define métodos de manipulação de eventos, RouterProtocol define métodos de navegação. Cada componente se comunica com outro apenas através de um protocolo, o que permite substituir facilmente implementações e testar isoladamente. Em média, um módulo VIPER para uma tela contém 5 protocolos + 5 classes + 1 Builder/Assembler = 11 arquivos por tela.

VIPER no Swift: Módulo, Router e Presenter

A construção de um módulo VIPER é feita no Builder (ou Assembler), que cria todos os cinco componentes e os conecta através de protocolos. O Builder é o único lugar onde os componentes conhecem os tipos concretos uns dos outros. Após a montagem, a View é retornada para exibição, o restante da cadeia é limpo e testado isoladamente.

swift
// Protocolo — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

// Protocolo — Interactor
protocol UserInteractorProtocol {
    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}

// Protocolo — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — lógica de negócios
final class UserInteractor: UserInteractorProtocol {
    private let service: UserService

    init(service: UserService) { self.service = service }

    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
        service.fetchUser(id: id, completion: completion)
    }
}

// Presenter — preparação de dados
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let interactor: UserInteractorProtocol
    private let router: UserRouterProtocol

    init(interactor: UserInteractorProtocol, router: UserRouterProtocol) {
        self.interactor = interactor
        self.router = router
    }

    func setView(_ view: UserViewProtocol) {
        self.view = view
    }

    func viewDidLoad() {
        view?.showLoading()
        interactor.fetchUser(id: 42) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(name: user.name)
                self.view?.display(email: user.email)
            case .failure(let error):
                // tratamento de erro
            }
        }
    }
}

// Router — navegação
final class UserRouter: UserRouterProtocol {
    private weak var viewController: UIViewController?

    func setViewController(_ vc: UIViewController) {
        viewController = vc
    }

    func navigateToProfile(userId: Int) {
        let profileModule = ProfileModuleBuilder.build(userId: userId)
        viewController?.navigationController?.pushViewController(profileModule, animated: true)
    }
}

// Builder — montagem do módulo
enum UserModuleBuilder {
    static func build(userId: Int) -> UIViewController {
        let service = UserService()
        let interactor = UserInteractor(service: service)
        let router = UserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        let viewController = UserViewController(presenter: presenter)
        presenter.setView(viewController)
        router.setViewController(viewController)
        return viewController
    }
}

Builder/Assembler é um elemento chave do VIPER, implementando Injeção de Dependência manualmente. A injeção de dependências através do construtor (constructor injection) garante que um componente não possa ser criado sem suas dependências. No VIPER moderno, o Builder pode usar Swinject (container DI), mas a montagem manual permanece mais transparente para testes. Na IT Sectr, aplicamos VIPER com montagem manual para módulos com lógica complexa — isso simplifica a leitura de código para novos desenvolvedores.

Mapper (Formatter) é um sexto componente opcional do VIPER. O Mapper transforma Entity (modelos BD/servidor) em ViewModel (modelos de exibição). Entity contém UserDTO com campos id, first_name, last_name, email. ViewModel — UserDisplayItem com name (first_name + last_name) e email. O Mapper é executado no Presenter. Se o mapeamento de dados for complexo (várias Entity → um ViewModel), o Mapper é extraído para uma classe separada para testes.

Comunicação entre módulos VIPER

Os módulos VIPER são isolados e não se conhecem. A comunicação entre módulos ocorre através do Router. Quando um usuário clica no botão "Perfil" na tela do usuário, o Presenter chama router.navigateToProfile(userId: 42). O Router cria um novo módulo através de ProfileModuleBuilder.build(userId: 42) e o abre via navigationController.push. Fluxo de dados: Módulo A → Router A → Builder do Módulo B → O Módulo B é criado e aberto.

Devolução de dados (por exemplo, selecionar uma cidade na tela de seleção → retornar à tela de edição de perfil) no VIPER é implementada através de delegados ou closures. O Módulo B define o protocolo ModuleBDelegate com o método didSelectCity(_ city: City). O Módulo A implementa este protocolo. O Router A passa o delegado para o Builder do Módulo B. Quando uma cidade é selecionada, o Módulo B chama delegate?.didSelectCity(city). Esta é uma prática padrão do iOS, familiar para qualquer desenvolvedor UIKit.

CenárioMecanismoExemplo
Navegação para frenteRouter → BuildernavigateToProfile(userId:)
Devolução de dadosDelegatedidSelectCity(_:)
Notificação do sistemaNotificationCenterUserDidLogout
Evento do InteractorPresenter → ViewMensagem WebSocket

NotificationCenter é usado para eventos do sistema (logout, mudança de plano, notificações push) que afetam vários módulos simultaneamente. Router ou AppDelegate se inscreve no Notification, cria o módulo necessário ou atualiza o estado. O VIPER não proíbe o NotificationCenter — é importante que ele seja usado apenas para eventos 1-to-many, enquanto para comunicação 1-to-1 são usados delegados ou closures.

Comparação do VIPER com MVVM e Clean Architecture

VIPER vs MVVM — VIPER requer 2–3 vezes mais código por tela, mas fornece isolamento absoluto de componentes. MVVM com ViewModel + SwiftUI é mais simples e rápido, mas escala pior para equipes de 5+ desenvolvedores. O VIPER define estritamente quem é responsável pelo quê: Interactor — apenas lógica de negócios, Presenter — formatação, Router — navegação. No MVVM, o ViewModel frequentemente cresce, assumindo navegação e lógica de negócios.

VIPER vs Clean Architecture — VIPER é um caso específico do Clean Architecture adaptado para iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller nos termos de Robert Martin. O Clean Architecture adiciona uma camada Gateway/Repository entre o Interactor e os dados, que geralmente não é separada no VIPER. Para projetos modernos em SwiftUI, a maioria das equipes escolhe Clean Architecture (The Composable Architecture) ou MVVM, deixando o VIPER para legados em UIKit.

Quando escolher VIPER — equipes de 5+ desenvolvedores, projetos UIKit com 50+ telas, requisitos de teste acima de 80%, apenas iOS (VIPER não é portável para Android sem reescrita). O VIPER fornece uma estrutura previsível: um novo desenvolvedor entende um módulo em 15 minutos. No entanto, a velocidade de desenvolvimento é 20–30% menor em comparação com MVVM devido ao maior número de arquivos. Na IT Sectr, usamos VIPER para projetos empresariais UIKit com equipes de 3+ pessoas e preferimos Clean Architecture para novos projetos em SwiftUI.

Testes de módulos VIPER

VIPER é projetado para testes — cada componente é testado isoladamente através de protocolos. O Interactor é testado com serviços mock: verifica-se que fetchUser é chamado com o ID correto e que o resultado é passado ao Presenter. O Presenter é testado com objetos mock de View e Interactor. O Router é testado com navegação mock: verifica-se que navigateToProfile é chamado com o userId correto e que o módulo correto é criado. A View é testada com testes de UI (XCUITest).

swift
import XCTest

final class UserPresenterTests: XCTestCase {
    func testViewDidLoad_callsFetchUserAndUpdatesView() {
        // Given
        let view = MockUserView()
        let interactor = MockUserInteractor()
        let router = MockUserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        presenter.setView(view)
        let expectedUser = User(id: 42, name: "John", email: "john@test.com")
        interactor.result = .success(expectedUser)

        // When
        presenter.viewDidLoad()

        // Then
        XCTAssertEqual(interactor.capturedUserId, 42)
        XCTAssertEqual(view.displayedName, "John")
        XCTAssertTrue(view.didShowLoading)
    }
}

Objetos mock para VIPER são criados manualmente (classe com propriedades de captura armazenadas) ou através de bibliotecas como Cuckoo / Mockingbird. Classes mock manuais são mais simples e claras, especialmente para treinar novos desenvolvedores. Cada mock armazena valores capturados (capturedUserId, displayedName) e flags de chamada (didShowLoading). No final do teste, não só se verifica que o método foi chamado, mas também com quais parâmetros — isso dá confiança na correção do fluxo de dados.

Cobertura de código em projetos VIPER da IT Sectr atinge 85–95% para Interactor, 90–95% para Presenter, 70–80% para Router, 30–50% para View (através de testes de UI). A View é testada com testes de snapshot (SnapshotTesting, 1,5K estrelas) — isso é mais rápido que XCUITest e cobre mais casos. A cobertura geral de um projeto VIPER é geralmente 70–80%, que é maior que um projeto MVVM (50–65%), mas requer mais tempo para escrever testes (30–40% do tempo de desenvolvimento contra 20–25% no MVVM).

Perguntas frequentes

Quantos arquivos existem em um módulo VIPER?

No mínimo 11 arquivos: 5 protocolos (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementações (ViewController, Interactor, Presenter, Router, Entity) e Builder/Assembler. Com Mapper (Formatter) — 12–13. Para um projeto de 50 telas, são 550–650 arquivos apenas de módulos VIPER. MVVM requer 3 arquivos por tela (ViewModel, View, Model) — 150 arquivos para 50 telas.

Pode-se usar VIPER no Android?

Sim, teoricamente o VIPER é portável para Android, mas na prática não é usado — o Google recomenda MVVM com Jetpack. O VIPER foi criado para iOS UIKit, onde o ViewController é difícil de testar devido ao seu ciclo de vida. No Android, o Jetpack ViewModel resolve o problema de teste sem o isolamento do VIPER. O equivalente do VIPER no Android é o Clean Architecture com divisão em módulos/features.

Qual é a diferença entre VIPER e Clean Architecture?

VIPER é uma implementação específica do iOS do Clean Architecture. Interactor corresponde a Use Case, Entity — Domain Model, Presenter — camada de Presentation. O Clean Architecture adiciona Repository/Gateway entre o Interactor e os dados, que no VIPER geralmente são implementados dentro do Interactor. O Clean Architecture não prescreve Router — a navegação fica a critério da implementação.

Precisa-se de VIPER para projetos SwiftUI?

Não — SwiftUI é projetado para MVVM + Combine. VIPER no SwiftUI é redundante: cinco componentes por tela com UI declarativa é sobrecarga sem benefício. Para SwiftUI, escolha MVVM ou TCA (The Composable Architecture). O VIPER permanece relevante para legados UIKit e projetos onde iOS 12 e versões anteriores são a versão mínima.

Como passar dados entre módulos VIPER?

Através do Router. O Módulo A chama router.navigateToProfile(userId: id). O Router A cria o módulo B através do Builder, passa userId. A comunicação de retorno — através de um delegado: o módulo B define o protocolo ModuleBDelegate, o módulo A o implementa e o passa através do Router. Eventos do sistema (logout) — através do NotificationCenter.

Resumo

  • VIPER — cinco componentes com separação estrita: View, Interactor, Presenter, Entity, Router
  • Modularidade — cada tela é isolada, o Builder monta dependências via constructor injection
  • Router — remove a navegação do Presenter, resolvendo o problema de navegação no iOS
  • Interactor — lógica de negócios limpa sem UIKit, testada com testes unitários
  • Volume de código — 11+ arquivos por tela, desenvolvimento 20–30% mais lento que MVVM
  • Testes — 70–80% de cobertura, Interactor e Presenter testados via objetos mock
  • SwiftUI vs UIKit — VIPER para UIKit (legado), MVVM/TCA para SwiftUI

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também