Padrões de Projeto: Delegação
() translation by (you can also view the original English article)
O padrão de delegação esta entre os padrões mais comuns de desenvolvimento em iOS e OS X. É um padrão simples que é fortemente usada pelos frameworks da Apple e mesmo o mais simples aplicativo iOS aproveita da delegação para funcionar. Vamos começar olhando à definição da delegação.
1. O que é Delegação?
Definição
A definição do padrão de delegação é pequeno e simples. Isto é como a Apple define o padrão.
Um delegado é um objeto que age em nome de, ou em coordenação com, outro objeto quando este objeto encontra um evento em um programa.
Vamos dividir isso abaixo. O padrão de delegação envolve dois objetos, o objeto delegado e o delegante. A classe UITableView
, por exemplo, define uma propriedade delegate
para que ela delegue eventos. A propriedade delegate precisa estar em conformidade com o protocolo UITableViewDelegate
, que é definido no arquivo header da classe UITableView
.
Neste exemplo, a instância da table view é o objeto delegante. O delegado é geralmente uma view controller, mas pode ser qualquer objeto que esteja em conformidade com o protocolo UITableViewDelegate
. Se você não esta familiarizado com protocolos, uma classe estará em conformidade se implementar os métodos exigidos do protocolo. Vamos olhar um exemplo um pouco mais tarde.
Quando o usuário toca em uma linha na table view, o table view notifica seu delegado enviando uma mensagem de tableView(_:didSelectRowAtIndexPath:)
. O primeiro argumento do método é a table view enviando a mensagem. O segundo argumento é o índice da linha que o usuário tocou.
A table view apenas notifica seu delegado neste evento. Cabe ao delegado decidir o que precisa acontecer quando um evento ocorrer. Esta separação de responsabilidades, como você aprenderá em instantes, é um dos principais benefícios do padrão de delegação.
Vantagens
Reusabilidade
Delegação tem diversas vantagens, a primeira sendo a reusabilidade. Como a table view delega a interação do usuário para seu delegado, a table view não precisa saber o que precisa ser feito quando uma linha for tocada.
Em outras palavras, a table view pode permanecer ignorante sobre a implementação dos detalhes de como a interação do usuário é tratada pelo aplicativo. Esta responsabilidade é passada ao delegado, uma view controller por exemplo.
Um benefício direto é que a classe UITableView
pode ser usada em muitas situações. A maior parte do tempo, não há necessidade para a subclasse UITableView adapta-lo para a necessidade do seu aplicativo.
Baixo Acoplamento
Outra vantagem importante da delegação é o baixo acoplamento. Em meu artigo sobre singletons, eu enfatizei que o alto acoplamento deve ser evitado o máximo possível. Delegação é um padrão de projeto que ativamente promove o baixo acoplamento. O que quero dizer com isso?
A classe UITableView
é acoplada ao seu delegado para fazer o trabalho. Se nenhum delegado for associado com a table view, a table view não manuseará ou responderá a interação do usuário. Isto significa que é preciso haver um certo nível de acoplamento. A table view e seu delegado, no entanto, são de baixo acoplamento, porque cada classe que implementa o protocolo UITableViewDelegate
pode atuar como delegado da table view. O resultado é um objeto gráfico flexível e de baixo acoplamento.
Separação de Responsabilidades
Uma vantagem menos conhecida da delação é a separação de responsabilidades. Sempre que você criar um objeto gráfico, é importante saber quais objetos são responsáveis por quais tarefas. O padrão de delegação deixa isto muito claro.
No caso da classe UITableView
, o delegado da table view é responsável por manipular a interação do usuário. A table view é responsável por detectar a interação do usuário. Está é uma clara separação de responsabilidade. Tal separação torna o seu trabalho como desenvolvedor muito mais fácil e claro.
2. Exemplo
Existem alguns detalhes no padrão de delegação. Vamos continuar explorando mais o protocolo UITableViewDelegate
.
Delegação
O protocolo UITableViewDelegate
precisa ser implementado pelo delegado da table view. A table view notifica seu delegado sobre a interação do usuário através do protocolo UITableViewDelegate
, mas ela também usa o delegado para seu layout.
Uma diferença importante entre o Swift e o Objective-C é a possibilidade de marcar métodos de protocolo como opcionais. Em Objective-C, os métodos de um protocolo são obrigatórios por padrão. Os métodos do protocolo UITableViewDelegate
, entretanto, são opcionais. Em outras palavras, é possível uma classe estar em conformidade com o protocolo UITableViewDelegate
sem a implementação de todos os métodos do protocolo.
Em Swift, entretanto, para uma classe estar em conformidade à um protocolo em particular é obrigatório implementar todos os métodos definidos pelo protocolo. Isto é muito seguro uma vez que o objeto delegante não precisa verificar se o delegado implementa um método do protocolo. Esta sutil, mas importante, diferença será ilustrada adiante neste tutorial quando implementarmos o padrão de delegação.
Data Source
Este é outro padrão que é claramente relacionado com o padrão de delegação, o padrão data source. O protocolo UITableViewDataSource
é um exemplo deste padrão. A classe UITableView
expõe uma propriedade dataSource
que é do tipo UITableViewDataSource
(id<UITableViewDataSource>
em Objective-C). Isso significa que a fonte de dados da table view pode ser qualquer objeto que implemente o protocolo UITableViewDataSource
.
O objeto data source é responsável por gerenciar a fonte de dados do objeto que é o data source. É importante notar que o objeto data source é responsável por manter uma referência do item que ele expões ao objeto destino, tal como uma table view ou um collection view.
Uma table view, por exemplo, pede à seu data source por um dado que precisa exibir. A table view não é responsável por manter um domínio do objeto de dados que precisa exibir. Esse papel é entregue para o objeto data source.
O padrão data source se encaixa bem no padrão Model-View-Controller ou MVC. Por que? Uma table view, por exemplo, faz parte da camada view. Ela não sabe e não deveria saber sobre a camada model e não é responsável por lidar com os dados que está chegando da camada model. Isso implica que a base de dados de uma table view, ou qualquer outra view que implemente o padrão data source, é frequentemente um controlador do algum tipo. Em iOS, normalmente é uma subclasse UIViewController
.
As assinaturas dos métodos de um protocolo data source segue o mesmo padrão como aqueles de um protocolo delegate. O objeto que está enviando a mensagem para o data source é passado como primeiro argumento. O protocolo data source só deve definir métodos que referem-se aos dados que está sendo utilizados pelo objeto requerente.
Uma table view, por exemplo, pede ao data source pelo número de seções e linhas que deve exibir. Mas ela também informa ao data source que uma linha ou seção foi inserida ou deletada. O último é importante, ja que o data source precisa atualizar-se para refletir as alterações visíveis na table view. Se a table view e o data source estiverem fora de sincronia, coisas ruins acontecem.
3. Implementação
Objective-C
Implementar o padrão de delegado é bem simples, agora que sabemos como funciona. Dê uma olhada a seguir um exemplo em Objective-C.
1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@protocol AddItemViewControllerDelegate; |
4 |
|
5 |
@interface AddItemViewController : UIViewController |
6 |
|
7 |
@property (weak, nonatomic) id<AddItemViewControllerDelegate> delegate; |
8 |
|
9 |
@end
|
10 |
|
11 |
@protocol AddItemViewControllerDelegate <NSObject> |
12 |
- (void)viewControllerDidCancel:(AddItemViewController *)viewController; |
13 |
- (void)viewController:(AddItemViewController *)viewController didAddItem:(NSString *)item; |
14 |
|
15 |
@optional
|
16 |
- (BOOL)viewController:(AddItemViewController *)viewController validateItem:(NSString *)item; |
17 |
@end
|
Declaramos uma classe, AddItemViewController
, que herda da UIViewController
. A classe declara uma propriedade, delegate
, do tipo id<AddItemViewControllerDelegate>
. Note que a propriedade está marcada como weak (fraca), que indica que uma instância AddItemViewController
mantem uma fraca referência do delegado.
Observe também que adicionei uma declaração do protocolo, após a instrução de importação do framework UIKit. Isto é necessário para evitar um aviso do compilador. Nós poderíamos mudar a declaração do protocolo para antes da instrução de importação, mas prefiro colocá-lo antes interface da classe. Isso não é nada mais do que uma preferência pessoal.
A declaração do protocolo também é muito simples. O protocolo AddItemViewControllerDelegate
herda o protocolo do NSObject
. Isto não é obrigatório, mas isso irá provar ser muito útil. Vamos descobrir por que isso um pouco mais tarde.
O protocolo AddItemViewControllerDelegate
declara dois métodos obrigatórios e um método opcional. Como eu mencionei anteriormente, é uma boa prática passar o objeto de delegação como primeiro parâmetro de todos os métodos para informar qual objeto está enviando a mensagem.
Os métodos obrigatórios notificam ao delegado sobre um evento, um cancelamento ou uma adição. O método opcional pede ao delegado por um feedback. Isso espera que o delegado retorne Yes
or No
.
Isto é a primeira peça do quebra cabeça da delegação. Temos que declarar uma classe que declare uma propriedade delegate
e temos que declarar um protocolo delegado. A segunda peça do quebra cabeça é chamar os métodos delegados na classe AddItemViewController
. Vamos ver como isso funciona.
Na implementation da classe AddItemViewController
, implementaremos uma action cancel:
. Essa action deve ser ligada a um botão na interface do usuário. Se o usuário tocar o botão, o delegado é notificado deste evento e, como resultado, o delegado pode dispersar a instância AddItemViewController
.
1 |
- (IBAction)cancel:(id)sender { |
2 |
if (self.delegate && [self.delegate respondsToSelector:@selector(viewControllerDidCancel:)]) { |
3 |
[self.delegate viewControllerDidCancel:self]; |
4 |
}
|
5 |
}
|
É recomendado verificar se o objeto delegado não é igual a nil
e se ele implementa o método delegado que vamos chamar, viewControllerDidCancel:
. Isso é fácil graças ao método respondsToSelector:
, declarado no protocolo NSObject
. Essa é a razão pela qual o protocolo AddItemViewControllerDelegate
herda o protocolo NSObject
. Herdando o protocolo de NSObject
, temos essa funcionalidade gratuitamente.
Você pode omitir a verificação se a propriedade delegate é igual a nil
, já que respondsToSelector:
irá retornar nil
se a propriedade delegate for nil
. Eu geralmente adiciono está verificação ja que ela exibe claramente o que estamos testando.
A terceira e ultima peça do quebra cabeça é a implementação do protocolo delegado pelo objeto delegado. O código a seguir demonstra a criação de uma instância AddItemViewController
e a implementação de um dos métodos delegados.
1 |
- (IBAction)addItem:(id)sender { |
2 |
// Initialize View Controller
|
3 |
AddItemViewController *viewController = [[AddItemViewController alloc] init]; |
4 |
|
5 |
// Configure View Controller
|
6 |
[viewController setDelegate:self]; |
7 |
|
8 |
// Present View Controller
|
9 |
[self presentViewController:viewController animated:YES completion:nil]; |
10 |
}
|
1 |
- (void)viewControllerDidCancel:(AddItemViewController *)viewController { |
2 |
// Dismiss Add Item View Controller
|
3 |
...
|
4 |
}
|
Não se esqueça de garantir a conformidade da classe que atua como delegado com o protocolo de AddItemViewControllerDelegate
, como mostrado abaixo. Você pode adicionar na interface da classe ou em uma extensão de classe privada.
1 |
#import "AddItemViewController.h"
|
2 |
|
3 |
@interface ViewController () <AddItemViewControllerDelegate> |
4 |
|
5 |
@end
|
Swift
Em Swift, o padrão de delegação é igualmente fácil de implementar e você irá notar que o Swift torna a delegação ligeiramente mais elegante. Vamos implementar o exemplo acima em Swift. Assim é como a classe AddItemViewController
fica em Swift.
1 |
import UIKit |
2 |
|
3 |
protocol AddItemViewControllerDelegate: NSObjectProtocol { |
4 |
func viewControllerDidCancel(viewController: AddItemViewController) |
5 |
func viewController(viewController: AddItemViewController, didAddItem: String) |
6 |
func viewController(viewController: AddItemViewController, validateItem: String) -> Bool |
7 |
}
|
8 |
|
9 |
class AddItemViewController: UIViewController { |
10 |
var delegate: AddItemViewControllerDelegate? |
11 |
|
12 |
func cancel(sender: AnyObject) { |
13 |
delegate?.viewControllerDidCancel(self) |
14 |
}
|
15 |
}
|
A declaração do protocolo é um pouco diferente em Swift. Note que o protocolo AddItemViewControllerDelegate
herda o NSObjectProtocol
ao invés do protocolo NSObject
. Em Swift, classes e protocolos não podem ter o mesmo nome, é por isso que o protocolo NSObject
tem um nome diferente em Swift.
A propriedade delegate
é uma variável do tipo AddItemViewControllerDelegate?
. Note o ponto de interrogação no final do nome do protocolo. A propriedade delegate é um opcional.
No método cancel(_:)
, chamamos o método delegado viewControllerDidCancel(_:)
. Esta única linha demonstra como o Swift pode ser elegante. Podemos desempacotar a propriedade delegate
por segurança antes de chamar o método delegado. Não é necessário verificar se o delegado implementa o método viewControllerDidCancel(_:)
já que todos os métodos do protocolo são obrigatórios em Swift.
Agora vamos olhar a classe ViewController
, que implementa o protocolo AddItemViewControllerDelegate
. A interface demonstra-nos que a classe ViewController
e herda a classe UIViewController
e adota o protocolo AddItemViewControllerDelegate
.
1 |
import UIKit |
2 |
|
3 |
class ViewController: UIViewController, AddItemViewControllerDelegate { |
4 |
func addItem(send: AnyObject) { |
5 |
// Initialize View Controller
|
6 |
let viewController = AddItemViewController() |
7 |
|
8 |
// Configure View Controller
|
9 |
viewController.delegate = self |
10 |
|
11 |
// Present View Controller
|
12 |
presentViewController(viewController, animated: true, completion: nil) |
13 |
}
|
14 |
|
15 |
func viewControllerDidCancel(viewController: AddItemViewController) { |
16 |
// Dismiss Add Item View Controller
|
17 |
...
|
18 |
}
|
19 |
|
20 |
func viewController(viewController: AddItemViewController, didAddItem: String) { |
21 |
|
22 |
}
|
23 |
|
24 |
func viewController(viewController: AddItemViewController, validateItem: String) -> Bool { |
25 |
|
26 |
}
|
27 |
}
|
No método addItem(_:)
, inicializamos uma instância da classe AddItemViewController
, definimos a propriedade delegate
e a apresentamos ao usuário. Note que temos todos os métodos delegados do protocolo AddItemViewControllerDelegate
implementados. Se não tivemos, o compilador irá informar a nós que a classe ViewController
não está em conformidade com o protocolo AddItemViewControllerDelegate
. Tente isto comentando um dos métodos delegado.



Conclusão
Delegação é um padrão que você vai se deparar com frequência quando estiver desenvolvendo aplicativos iOS e OS X. O Cocoa depende muito desse padrão de projeto e é importante familiarizar-se com ele.
Desde a introdução dos blocos, há alguns anos, a Apple lentamente oferece uma API alternativa baseada em blocos para algumas implementações de delegação. Alguns desenvolvedores seguiram a liderança da Apple, oferecendo suas próprias alternativas baseadas em blocos. A popular biblioteca AFNetworking, por exemplo, baseia-se fortemento em blocos ao invés de delegação, resultando em uma elegante e intuitiva API.
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!