Coloque sua UIViewController em uma dieta

Roger Oba
Arquitetura iOS
Published in
7 min readNov 21, 2015

--

Odeio dizer isso, mas nossas view controllers têm ficado "acima do peso", e é nossa culpa! Nós as alimentamos com protocolos em cima de protocolos, regras de negócio em cima de regras de negócio… Elas não sabem de nada, tadinhas, e acabam engolindo tudo.

Essa será uma série de 3 artigos para tunar suas table views, onde iremos pegar aquela sua view controller velha e rechonchuda, refatorá-la para deixá-la em forma, revigorá-la com um pouco de MVVM e fazê-la voar com uma melhor gestão de operações assíncronas!

Nessa primeira parte, usando um pouco de refatoração e umas manhas do Interface Builder espero dar motivação para que iniciem sua jornada de deixar suas view controllers voando ;)

A situação atual…

UITableViews estão presentes na maioria dos apps, então irei usá-las de exemplo para mostrar como uma view controller pode ficar mais leve e bem organizada.

Dê uma olhada no projeto que estaremos otimizando aqui. Iremos trabalhar em cima da classe TableViewController mais especificamente.

Projeto de exemplo que utilizaremos.

No esquema que estão as coisas, é uma table view controller super simples, que talvez você possa até falar que nem está tão ruim assim.

Mas infelizmente no mundo real as coisas raramente são tão simples assim. Claro que pode ser que comecem simples, mas pouco a pouco novas funcionalidades são adicionadas e, como as mães dizem: é melhor prevenir do que remediar.

Então vejamos quais passos podemos seguir para tornar essa table view um pouco melhor…

Reconhecendo o território

Vamos dar uma olhada no que essa view controller está fazendo atualmente:

  • Está fazendo uma chamada de API pra baixar dados pra popular a tabela (completa, utilizando thread)
  • Está controlando o data source da table view
  • E lidando com os métodos do delegate da table view.

Isso é um monte de coisa que não tem nada a ver com a view em si. De fato, se olharmos bem, parece que a maioria do código só lida com os métodos do delegate e regras de negócios. :(

Começando pelas pontas

Olhando esse código parece que a chamada da API vai ser a parte mais fácil de se livrar primeiro.

Método reloadData em TableViewController.m

Esse código é até meio grosseiro… Além de ser muito extenso, ele mistura uma chamada de API com a lógica do UIRefreshControl e também ambos os casos de sucesso e fracasso da requisição na web.

Nós devemos debulhar esse código e torná-lo mais reutilizável.

DataManager, uma nova classe a ser criada, que herda de NSObject.

Está um pouco melhor agora que podemos utilizar uma instância de DataManager em qualquer view controller que precisar.

Note que o método (dataWithContentsOfURL:) se trata de uma chamada síncrona e bloqueará a thread em que estiver rodando, mesmo que seja em background. Saibam que não é recomendável o uso dela, mas deixaremos assim propositalmente por enquanto, pois trataremos performance no 3º capítulo da série.

Agora precisamos criar uma property para o DataManager na nossa view controller:

@property (nonatomic, strong) DataManager *dataManager; 

E criar uma instância dela no viewDidLoad:

self.dataManager = [DataManager new];

Finalmente, substituímos o nosso método reloadData da view controller:

Método reloadData, agora refatorado.

E voltaremos nisso mais tarde.

Excesso de protocolos

Provavelmente o que mais entope nossas view controllers são os protocolos que precisamos usar para as table views, text fields, date pickers, etc… Certamente não estou criticando o uso do padrão de delegates, eles são ótimos em nos permitir controlar como as coisas devem funcionar, mas temos o hábito de jogar tudo dentro da view controller, precisamos parar com isso!

Então como fazer isso? Bom, existe um atributo pouco conhecido do Interface Builder que permite que adicionemos qualquer objeto na nib/storyboard. Esses objetos serão criados quando sua view for, e estarão prontos para serem usados quando a viewDidLoad for chamada.

Vamos ver como isso funciona. Vamos criar objetos separados para ambos os nossos UITableViewDataSource e UITableViewDelegate.

Crie o seguinte:

Crie o TableDataSource, uma subclasse de NSObject
Crie o TableDelegate, também subclasse de NSObject

Em adição aos novos objetos criados, criaremos também um outro objeto para coordenar a atualização de dados.
Caso você utilize nib para criar suas células ao invés de criar diretamente na storyboard, você deve obrigatoriamente registrar as células para reutilizarem a sua nib. Nesse projeto estamos utilizando storyboard (preferência pessoal), mas caso utilize nib, esse é o local ideal para registrar suas células.

Por fim, crie a última subclasse de NSObject, TableCoordinator

De volta à nossa view controller, precisamos adicionar agora uma property para nosso TableCoordinator novinho em folha:

@property (nonatomic,strong) IBOutlet TableCoordinator *tableViewCoordinator;

E também um IBAction (ainda na view controller) para quando a célula for selecionada:

Novo método itemPressed: que substituirá o método tableView:didSelectRowAtIndexPath: do delegate.

Por último, é necessário limpar tudo o que agora está sobrando na view controller:

  • Delete o #import "TableCell.h"
  • Remova a declaração do static kURL
  • Remova os protocolos <UITableViewDataSource,UITableViewDelegate>
  • Remova as properties NSArray *data e UITableView *tableView
  • Remova todo o código que está sob o #pragma mark UITableViewDataSource e UITableViewDelegate
  • No viewDidLoad, substitua o título por Exemplo Saudável. Você merece ;)
  • E procure essas linhas no método reloadData:
self.data = items;
[self.tableView reloadData];

Substitua por:

[self.tableViewCoordinator reloadData:items];

Observadores notarão que temos vários itens declarados com IBOutlets nestes últimos objetos que criamos, além da última IBAction que permite que controlemos a seleção da célula só por associá-la!

Voltando à storyboard, desconecte todos os outlets relacionados à view controller (menos a view ou table view (caso esteja usando uma UIViewController ou UITableViewController, respectivamente), que não será possível).
Agora vá no menu onde você normalmente adiciona novos objetos à storyboard, procure um item com nome Object e adicione 3 deles à sua view controller.

Objeto que você deve adicionar à sua view controller.
Adicione 3 Objects à sua view controller.

Agora para cada um deles, abra o menu de Identity Inspector, pois iremos alterar suas classes. Faça com que respondam pelas classes TableDelegate, TableDataSource, e TableCoordinator.

Altere as classes dos objetos para os 3 novos objetos que criamos.

Agora vem a parte divertida: conectar tudo! Faça as seguintes conexões:

  • View Controller ~> TableCoordinator
  • TableCoordinator ~> TableDelegate
  • TableCoordinator ~> TableDataSource
  • TableCoordinator ~> Table View
  • Table View ~> TableDelegate
  • Table View ~> TableDataSource
  • TableDelegate ~> Método (itemPressed:) da View Controller

Ufa, foram muitas conexões… Mas já conseguimos nos livrar de boa parte do código que estava na nossa view controller. Agora olhe o quão mais leve ela está!

#Partiu dar uma volta!

Puxe o gatilho e faça um test drive com sua nova view controller. Ela faz exatamente o que fazia antes, mas seu código está muito mais limpo, organizado e, principalmente, livramos nossa view controller de um monte de trabalho que não pertencia a ela!

Sei o que você deve estar pensando. Tivemos esse trabalho todo, para não haver nenhum ganho em performance. Aparentemente apenas complicamos mais a arquitetura do nosso projeto, apenas para livrar a view controller de código, mas pra quê?! A fim de quê exatamente?

Vimos no começo desse blog que o exemplo utilizado para demonstrarmos essas práticas é bem simples, mas na vida real raramente encontramos projetos simples assim. Geralmente são dezenas de view controllers, e cada uma responsável inúmeras views, protocols, properties, data sources, trabalhos em background, chamadas de API, etc, etc. Façamos esse favor a nós mesmos, e aprendamos a deixar nossas view controllers apenas responsáveis pelo o que elas deveriam ser: controlar nossas views! Nada mais.

Essa organização do código, e melhoria na arquitetura do projeto é o que destacará um desenvolvedor organizado, facilitará a adaptação de novos desenvolvedores que podem vir a trabalhar no seu projeto, facilitará a manutenção, e no próximo blog vocês verão que isso também tornará mais eficiente a testabilidade do seu app. E isso é fundamental em todo projeto!

Se você quiser dar uma olhada em como ficou o nosso projeto-exemplo após as melhorias, você pode encontrá-lo aqui.

A nossa table view ainda tem muito caminho pela frente. Ela está travando quando deslizamos, mas a performance será tratada em nosso 3º capítulo da série. Por enquanto, aguarde o 2º capítulo: MVVM!

Créditos

100% dos créditos vão para o desenvolvedor Ian, por publicar o conteúdo original deste artigo no blog do time global de desenvolvedores iOS. O artigo original pode ser encontrado aqui.

Se gostaram desse artigo, compartilhem com seus colegas de trabalho e amigos. Isso me motivará a publicar mais séries de artigos.

Pretendo trazer artigos em português para contribuir com a comunidade de desenvolvedores brasileiros, utilizando uma linguagem bem informal e descontraída ;)

Não se esqueçam de clicar no coração aqui embaixo, que não sei exatamente pra que serve, mas com certeza deve ser algo bom. E talvez bookmark também porque vai que… né.

--

--

Roger Oba
Arquitetura iOS

Lead iOS Engineer @ Tellus Inc. I hate reading, but Medium has been doing a great job changing this. I might write my opinions here every once in a while.