Felipe Cypriano

Você está prestes a ler

Desmistificando Delegates No iOS

Uma das maiores dificuldades de quem está começando a desenvolver para iOS é entender a ligação entre as várias partes do SDK. Como o primeiro contato costuma ser com o UIKit, enteder e saber usar os delegates é essencial, pois eles são a forma usada pelo UIKit para ligar os componentes da View ao Controller.

A documentação da Apple diz o seguinte sobre delegates:

Delegação é um padrão simples e poderoso em que um objeto age no lugar de, ou em coordenação com, outro objeto.

Ou seja, é uma forma de comunicação entre dois, e somente dois, objetos. Entender que somente dois objetos estão envolvidos é o que diferencia o uso de delegates das duas outras formas de comunicação no iOS: Notification e Key Value Observing (KVO).

Neste artigo falarei sobre como usar os delegates que vem no iOS e a partir desse conhecimento entender como e quando utilizar esse padrão nos seus próprios objetos.

Delegates no UIKit

Várias das classes do UIKit usam delegates, é a forma de ligação entre os componentes e os controllers. Normalmente a classe que possui um delegate tem uma propriedade chamada delegate e o nome do Protocol com sufixo “Delegate”. Alguns componentes mais complexos possuem dois delegates sendo um deles responsável pelos dados que serão exibidos, a propriedade deste delegate costuma ser chamada de dataSource e seu Protocol tem sufixo “DataSource”.

Componente Delegate(s)
UITextField UITextFieldDelegate
UIPicker UIPickerDelegate e UIPickerDataSource
UIScrollView UIScrollViewDelegate
UITableView UITableViewDelegate e UITableViewDataSource

O UITableView é a forma padrão de listar dados no iOS, essa classe é responsável por exibir a lista na tela e interpretar as interações que o usuário faz com a lista, é isso que ela faz e faz bem. As outras duas importantes funções da lista são delegadas: obtenção dos dados da lista e tratamento de eventos. Essa classe é uma das que mais delega funções e é por isso que eu gosto de usá-la como exemplo e como inspiração na criação dos meus próprios delegates.

Delegação dos dados - UITableViewDataSource

Para não ficar preso a uma listas de algum determinado tipo de dado, como strings, o UITableView delega essa responsabilidade através do protocol UITableViewDataSource. Com isso é possível mostrar qualquer tipo de dado sem precisar criar uma subclasse para cada variação.

Normalmente UITableViewDataSource é implementado no controller, já que é dele a responsabilidade de ligar a view ao model. Mas nada impede que o delegate seja implementado em uma subclasse simples deNSObject.

Criando uma UITableView por código
1
2
3
4
5
6
7
- (void)viewDidLoad {
  [super viewDidLoad];
  UITableView *tableView = [[UITableView alloc] initWithFrame:[[self view] bounds]];
  tableView.dataSource = self;
  [[self view] addSubview:tableView];
  [tableView release];
}

No exemplo acima após a criação da UITableView sua propriedade dataSource é setada para self, indicando que o UIViewController que a criou vai ser o objeto responsável por ser a origem dos dados da lista. Então toda vez que sua table view precisar de um dado ela irá chamar o metódo corresponde no objeto que foi setado a propriedade dataSource. Por exemplo, para informar quantas linhas a table view tem você deve implementar o seguinte método:

1
2
3
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 10;
}

Neste caso a table view terá sempre 10 linhas. No uso real você irá retornar o valor com base no tamanho de sua lista. Agora que a quantidade de linhas já está definida é preciso implementar o método que retorna o que vai ser exibido em cada linha.

1
2
3
4
5
6
7
8
9
10
11
12
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellId = @"CellId";

    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:cellId];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId] autorelease];
    }

    cell.textLabel.text = [NSString stringWithFormat:@"Linha %d", [indexPath row]];

    return cell;
}

A table view chama esse método para cada linha que será exibida. E é responsabilidade do UITableViewDataSource o que será mostrado em cada linha, nesse exemplo simples somente uma lista de strings é exibida mas se quiser uma lista toda customizada é nesse método que você vai trabalhar.

Esses dois métodos são os únicos obrigatórios no UITableViewDataSource, mas existem outros muito úteis basta dar uma olhada na documentação.

Respondendo a eventos - UITableViewDelegate

Com a sua lista exibindo os dados é preciso fazê-la responder a eventos, como selecionar uma determinada linha. O resposável por fazer isso na UITableView é o UITableViewDelegate, esse delegate é muito completo e vale a pena dar uma boa olhada na documentação.

1
tableView.delegate = self;

Para setar o delegate basta incluir o código acima no método viewDidLoad criado anteriormente. Diferente do UITableViewDataSource todos os métodos do UITableViewDelegate são opcionais. O método abaixo é chamado sempre que uma linha da lista é selecionada:

1
2
3
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  NSLog(@"Linha %d foi selecionada pelo usuário.", [indexPath row]);
}

O uso dos delegates é muito similar e sem segredos.

Criando Seus Próprios Delegates

Saber usar delegates do SDK é somente a primeira parte, é essencial saber quando e como criar os seus próprios.

Nomenclatura

O primeiro ponto que considero muito importante ao criar seus delegates é utilizar corretamente o padrão de nomenclatura, que é bem simples. Supondo que você tenha criado uma classe chamada VerticalButtonListView que gera uma lista de botões na vertical com base em um array de nomes, é preciso delegar a ação de quando ocorrer um tap em dos botões.

O nome do delegate é o nome da classe com o sufixo “Delegate”: VerticalButtonListViewDelegate. O nome dos métodos devem ter sempre o primeiro parametro a instância que gerou a chamada. Logo o primeiro parâmetro de todos os métodos do delegate VerticalButtonListViewDelegate deve ser:

1
verticalButtonListView:(VerticalButtonListView *)verticalButtonListView

A ação que precisamos delegar na view VerticalButtonListView é o que será feito quando houver um tap em um determinado botão. Como podem haver vários botões na lista é preciso informar qual botão gerou a chamada, faremos isso passando o índice do botão na lista:

1
- (void)verticalButtonListView:(VerticalButtonListView *)verticalButtonListView buttonTappedAtIndex:(NSInteger)index;

Neste momemnto muitos podem se perguntar, porque não criar algo menor só com “o que importa”?

1
- (void)buttonTappedAtIndex:(NSInteger)index;

Problemas de ter um delegate com um método como o acima:

  1. Somente metade das informações está sendo passada pro delegate.
  2. Imagine uma tela que tem duas listas diferentes, como você vai saber de qual lista veio a ação?
  3. Se no futuro você criar o HorizontalButtonListView, como vai diferenciar os métodos de dois delegates diferentes?

Resumindo, não deixe de passar o objeto que gerou a ação nos métodos do seu delegate utilizando o mesmo nome da classe do objeto.

Implementando

Delegates são implementados em Objective-C através de Protocols.

Protocols declaram métodos que podem ser implementados por qualquer classe.

Eu prefiro dizer que Protocols são definições de interfaces cujas implementações devem ser feitas por outras classes. O Protocol nunca implementa nenhum método. Então, o delegate é uma interface, ou conjunto de métodos, que outra classe irá implementar. Assim o objeto “delegador” sabe exatamento qual método chamar no objeto “delegado”.

VerticalButtonListView.h
1
2
3
4
5
6
7
8
@protocol VerticalButtonListViewDelegate <NSObject>
- (void)verticalButtonListView:(VerticalButtonListView *)verticalButtonListView buttonTappedAtIndex:(NSInteger)index;
@end

@interface VerticalButtonListView : NSObject
@property (nonatomic, assign) id<VerticalButtonListViewDelegate> delegate;
// ...
@end

O código acima mostra como declarar o delegate VerticalButtonListViewDelegate que é um Protocol que herda do protocol NSObject.

1
@protocol VerticalButtonListViewDelegate <NSObject>

É importante fazer seus Protocols herdarem de NSObject pois na prática seus objetos vão mesmo serem subclasses de NSObject, definindo isso no Protocol faz com que o compilador não gere nenhum warning quando você precisar chamar um método de NSObject no delegate, como por exemplo description.

1
@property (nonatomic, assign) id<VerticalButtonListViewDelegate> delegate;

Para que VerticalButtonListView consiga chamar os métodos do delegate é preciso criar uma propriedade que irá guardar a referência para o objeto que implementa os métodos. A propriedade tem que ser assign, pois não faz sentido que a instância de VerticalButtonListView tenha ‘ownership’ sobre seu delegate. Não entendeu nada sobre ownership e assign? Então leia e aprenda tudo sobre gerenciamento de memória, é essencial.

Sempre que houver tap em um botão é preciso chamar o método do delegate. Para fazer isso é simples basta colocar o seguinte código na action de cada botão:

VerticalButtonListView.m
1
2
3
4
5
- (void)buttonTouchedUp:(id)sender {
  NSInteger index = [self indexForButton:sender];

  [delegate verticalButtonListView:self buttonTappedAtIndex:index];
}

Com isso, todas vez que a ação do botão for executada a responsabilidade vai ser passada pro delegate para que ele decida o que fazer.

Há um recurso interessante no Protocol do Objective-C que é bem útil ao criar delegates: métodos opcionais. É possível definir métodos no delegate que são opcionais, como exemplo vamos criar um método que pergunta ao delegate se um botão pode ser “tocado”.

VerticalButtonListView.h
1
2
3
4
5
6
7
@protocol VerticalButtonListViewDelegate <NSObject>
- (void)verticalButtonListView:(VerticalButtonListView *)verticalButtonListView buttonTappedAtIndex:(NSInteger)index;

@optional
- (BOOL)verticalButtonListView:(VerticalButtonListView *)verticalButtonListView canTouchButtonAtIndex:(NSInteger)index;

@end

Todos os métodos declarados a partir da diretiva @optional serão opcionais. Um método opcional é um método em que o compilador não gera nenhum warning caso ele não tenha sido implementado. Sendo assim, não podemos chama-lo diretamente como fizemos com o verticalButtonListView:buttonTappedAtIndex:.

VerticalButtonListView.m
1
2
3
4
5
6
7
8
9
10
11
12
- (void)buttonTouchedUp:(id)sender {
  NSInteger index = [self indexForButton:sender];

  BOOL canBeTapped = YES;
  if ([delegate respondsToSelector:@selector(verticalButtonListView:canTouchButtonAtIndex:)]) {
      canBeTapped = [delegate verticalButtonListView:self canTouchButtonAtIndex:index];
  }

  if (canBeTapped) {
      [delegate verticalButtonListView:self buttonTappedAtIndex:index];
  }
}

O método respondsToSelector: é implementado pela classe NSObject e retorna YES caso o objeto tenha implementado o método. Caso o delegate não herdasse de NSObject o compilador iria colocar um warning na linha do if dizendo que o objeto delegate pode não responder a respondsToSelector:.

Concluindo

Delegates são essenciais para ligar os objetos da View com o Controller. Entender como criá-los e usá-los é essencial para fazer qualquer projeto no iOS. Use o bom senso e não deixe seus controllers virarem uma macarronada de delegates, sempre faça delegates com nomes que são auto explicativos e não economize no tamanho dos nomes não há vantagem nenhuma nisso.