Engenharia iOS

[Arquitetura iOS #1] MVC no iOS e o verdadeiro motivo dos Massive View Controllers

Ao desenvolver para iOS, quase todo mundo ouve esta piada pelo menos uma vez.

4 min de leitura
Imagem de capa de [Arquitetura iOS #1] MVC no iOS e o verdadeiro motivo dos Massive View Controllers

Ao desenvolver para iOS, quase todo mundo ouve esta piada pelo menos uma vez.

“MVC não significa Model-View-Controller, mas Massive View Controller.”

É uma piada, mas contém uma verdade. A Apple recomenda oficialmente essa arquitetura; então por que segui-la transforma os view controllers em monstros de milhares de linhas?

Nesta primeira parte da série sobre arquitetura iOS, vamos revisar como era o MVC originalmente e por que o iOS não segue esse modelo.

Em resumo, o problema não é o MVC, mas a estrutura do UIViewController, que fica entre View e Controller.


Como era o MVC originalmente?

MVC é um padrão muito antigo, criado no Smalltalk em 1979. Na forma original, suas três responsabilidades eram claramente separadas.

  • Model: Dados e lógica de negócio
  • View: Exibição da tela
  • Controller: Recebe a entrada do usuário e a encaminha ao Model

No MVC original, a View observa o Model diretamente. Quando o Model muda, a View é atualizada automaticamente.

O MVC da Apple é um pouco diferente. View e Model não conhecem um ao outro, e o Controller fica no meio, fazendo toda a mediação. Foi uma escolha razoável para aumentar a reutilização, mas também abriu espaço para sobrecarregar o Controller.


UIViewController já quebra as regras pelo próprio nome

No MVC da Apple, o Controller é implementado como UIViewController no iOS. Mas veja novamente o nome da classe: View + Controller. O próprio nome combina duas responsabilidades.

Na prática, UIViewController cuida de tudo isso.

  • Gerenciamento de viewDidLoad, viewWillAppear como o ciclo de vida da view
  • Processamento de eventos ligados à view, como rotação de tela e atualização de layout
  • Implementação de UITableViewDataSource, UITableViewDelegate como protocolos da view

Até aqui, ainda são tarefas relacionadas à view. O problema é não haver uma resposta clara para perguntas como “Onde escrevo as requisições de rede?”, “E a navegação?”, ou “A formatação de dados?”. Como não são Model nem View, tudo acaba no view controller.

É View + Controller no nome, e fazer tudo sozinho vira o padrão.
É View + Controller no nome, e fazer tudo sozinho vira o padrão.

A aparência típica de um view controller inchado

A estrutura comum de um view controller pode ser resumida em código assim.

final class ProfileViewController: UIViewController {
    // 1. Propriedades da view
    private let tableView = UITableView()

    // 2. Estado (na prática, um  Model cache)
    private var user: User?
    private var posts: [Post] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setupLayout()          // 3. Código de layout
        fetchProfile()         // 4. Requisições de rede
    }

    private func fetchProfile() {
        URLSession.shared.dataTask(...) { ... }  // 5. Parsing e tratamento de erros
    }

    @objc private func editTapped() {
        // 6. Até a navegação diretamente
        navigationController?.pushViewController(EditViewController(), animated: true)
    }
}

Layout, gerenciamento de estado, rede, parsing e navegação ficam todos no mesmo arquivo. Quando você adiciona implementações de delegate, ele passa rapidamente de mil linhas.

O custo real dessa estrutura não é o número de linhas, mas o fato de que os testes se tornam impossíveis. Até para verificar uma lógica simples como “o botão Editar fica oculto quando user é nil”, é preciso iniciar o UIViewController inteiro e simular seu ciclo de vida.


Então devemos abandonar o MVC?

Não necessariamente. Para telas pequenas, MVC ainda é a opção mais rápida e simples. Os frameworks da Apple foram projetados com MVC como base, então forçar outra estrutura pode gerar mais atrito.

O ponto central é, mesmo usando MVC, separar conscientemente as responsabilidades que podem sair do view controller.

  • Lógica de rede e dados → Objetos de serviço separados
  • Navegação → Objetos dedicados, como Coordinator
  • Configuração de células e formatação → Tipos específicos

Em algum momento, você também vai querer separar a lógica que cria “o estado a ser exibido na tela”. A resposta é MVVM, tema da próxima parte. Vamos explicar por que um ViewModel é necessário e por que, sem binding, ele fica incompleto.

O primeiro passo é separar conscientemente as responsabilidades que podem ser extraídas.
O primeiro passo é separar conscientemente as responsabilidades que podem ser extraídas.

Resumo

  • O MVC não tem culpa. O problema é a característica estrutural do iOS: UIViewController desempenha os papéis de View e Controller ao mesmo tempo.
  • O código que não é Model nem View fica sem destino e se acumula no view controller. Essa é a verdadeira identidade do Massive View Controller.
  • MVC continua válido para telas pequenas. Porém, rede, navegação e formatação devem ser separadas conscientemente.
  • Para separar também a lógica que cria o estado da view, é preciso usar MVVM. Veremos isso na próxima parte.

Leia também