Tais Rocha
Tais Rocha29/09/2023 10:37
Compartilhe

Closures e Delegates: similaridades e aplicações

  • #Swift

Closures e delegates

(Tempo de leitura: 10-15 min.)

E aí, pessoal!

Hoje irei falar a respeito de Closures e Delegates, mecanismos importantíssimos dentro do desenvolvimento iOS. Já adianto que esse será um artigo maior, mas tentei fazê-lo de forma bastante didática.

Confesso que Closure é um assunto que tive bastante dificuldade em aprender, então estou escrevendo esse artigo para ajudar, também, quem estiver sofrendo com esse assunto.

Closures e Delegates são duas formas de fazer comunicação entre objetos, mais especificadamente, “comunicação para trás” entre dois objetos.

Vou falar melhor como cada uma funciona, dar exemplos, mostrar códigos nativos do Swift que utilizam essas duas formas e, por fim, dar uma orientação de quando usar cada uma.

Porém, antes de tudo, vamos entender o que seria essa comunicação para trás.

Para começar vou explicar o que é a comunicação para frente. Vamos lá!

Para fins de entendimento, quando eu falar OBJETO, eu me refiro à classes e structs.

Comunicação pra frente é simplesmente quando vc chama uma função ou acessa uma propriedade em um outro objeto.

Como no exemplo abaixo, a FirstViewController cria e acessa uma função na SecondViewController. Isso é um comunicação simples entre dois objetos:

image

A comunicação entre as duas controllers acontece “para frente”, da esquerda para direita. A comunicação segue o fluxo da criação: FirstVC cria SecondVC e FirstVC acessa SecondVC.

E uma característica dessa comunicação, pelo menos quando segue boas práticas, é o segundo objeto (SecondVC) não saber nada sobre o primeiro (FirstVC).

O primeiro pode chamar funções no segundo, mas o segundo objeto não tem nenhuma referência do primeiro para chamar uma função nele se fosse necessário.

Vamos supor então que o segundo objeto precise chamar uma função no primeiro.

Quando clicar em um botão na SecondVC, eu devo atualizar a função refresh na FirstVC.

A primeira maneira que poderíamos fazer isso, é da seguinte forma:

image

  • Criar uma referencia para FirstViewController na SecondViewController (linha 42)
  • Injetar o valor dessa propriedade lá na função na FirstViewController (linha 20)
  • Chamar a função refresh da FirstViewController na SecondViewController (linha 49).

Essa seria uma comunicação para trás, pois a SecondViewController chama uma função na FirstViewController.

O objeto Criado, chama uma função no objeto Criador.

B chama uma função em A.

E esse código feito funcionaria? Sim.

Porém nunca faça dessa forma porque ela não segue várias orientações de boas práticas de desenvolvimento.

Não vou entrar na explicação do porquê (pois não é o objetivo desse artigo). Vou apenas falar sobre quais as formas certas de se fazer, que são, justamente, usando CLOSURES e DELEGATES.

Voltando ao nosso exemplo, estamos lidando com um problema de comunicação pra trás. Que é, repetindo, quando o objeto B precisa chamar uma função/propriedade em A.

E o objeto A precisa ser o criador do objeto B.

Vamos então resolver esse problema começando por CLOSURE.

Ficaria assim:

image

  • Criamos uma propriedade do tipo closure (linha 44)
  • Acionamos ela (linha 51)
  • E escutamos ela para realizar uma ação (linha 20-22)

Closures funcionam assim. São acionadas e escutadas.

E no nosso caso, quando ela for acionada no objeto B (SecondViewController), vai ser escutada no objeto A (FirstViewController) que consequentemente vai chamar a função refreshScreen.

Isso é comunicação para trás. O gatilho para execução de um código na FirstviewController foi disparado na SecondViewController.

A closure tem esse formato meio esquisito mesmo: () -> Void.

O que você precisa entender sobre esse formato é que vc coloca dentro dos parênteses aquilo que vc quer enviar para quem está escutando sua closure (no nosso caso, FirstVC).

No exemplo, não precisa enviar nada, porém vamos mudar. Vamos colocar que é necessário enviar uma string que foi carregada da SecondVC para a FirstVC.

Ficaria assim:

image

Um detalhe sobre closures, é que você pode escolher tanto abrir bloco de código direto no local onde ESCUTA a closure, como na linha 20-22 do nosso exemplo.

Ou você também pode passar uma função para ela, como na linha 23. 

A primeira forma faz o seguinte: Quando essa closure for acionada no Objeto B, eu vou executar esse bloco de código aqui em baixo no objeto A.

A segunda forma faz isso: Quando a closure for acionada, eu vou executar essa função.

As duas formas funcionam e você pode escolher a que faz mais sentido para você:

image

Agora vamos mostrar a semelhança entre closures e delegates.

Delegates

Delegate também é uma forma de comunicação para trás. É muito utilizada e uma forma que segue bons padrões de desenvolvimento.

Seguindo o exemplo dado na parte de closures, vamos fazer os mesmos passos.

De inicio queremos apenas chamar uma função no objeto A (firstVC) quando clicar no botão que aciona a função ‘’didClickRefreshHome’ no objeto B (SecondVC).

Então vamos lá.

Pra usar delegate, é só fazer uma passo a passo simples:

No objeto B:

- criar protocolo com função para se comunicar com ObjetoA

- criar propriedade week var delegate

- acionar delegate

Vamos ver como ficaria nosso codigo:

image

  • Fizemos o passo 1 nas linhas 38-40.
  • O passo 2 na linha 45.
  • O passo 3 na linha 52

O weak var é para não criar referencia cíclica e consequentemente causar Memory leak no nosso código. Recomendo ler sobre esse assunto.

Então estamos tentando resolver um problema de comunicação para trás. Onde o objeto B quer mandar informação para A.

Logo fizemos a primeira parte, no objeto B, que é a parte do acionamento. Agora temos que escutar no objeto A.

Vamos lá, é só seguir os seguintes passos:

1- no momento da criação do objeto B, atribuir delegate para self (após isso vai dar um erro q se resolve com o passo 2)

2 - extender o seu objeto com o protocolo do delegate (vai criar um outro erro, que vai resolver com o passo 3)

3 - implementar o método do protocolo

4 - fazer as ações que deseja 

Segue como ficaria nosso codigo:

image

  • Fizemos o passo 1 na linha 20.
  • O passo 2 na linha 37.
  • O 3 na 38.
  • E o 4 na 39.

Comento aqui que poderíamos colocar dentro da função refreshHome o que quisermos.

Vamos supor que nesse exemplo imaginário, quando clicar num botão na SecondviewController, tem que chamar um API na FirstViewController para carregar dados de saldo bancário para tela. Entao poderíamos fazer essa chamada de API dentro refreshHome. Ou colocar em uma outra função (refreshScreen) e chamar por lá. Dá na mesma.

E se quisermos mandar uma string de B para A (Second -> First) igual fizemos com closure?

Teríamos apenas que colocar um parâmetro em nossa função do delegate. 

Ficaria assim:

image

Closures e Delegates funcionam da mesma maneira. Mandam informações de B para A.

E funcionam nesse esquema de um lugar aciona, e outro escuta. O B aciona, e o A escuta.

Closures e delegates são muito usados pelo próprio iOS.

O delegate do sistema que mais usamos é o UITableViewDelegate.

Quando precisamos escutar na controller se uma célula foi clicada por exemplo, precisamos criar essa conexão entre objetos.

UITableView seria o objeto B, e a viewController o objeto A.

image

Repare que precisamos fazer os mesmo 4 passos de delegate para o objeto A.

Nesse caso não fazemos a parte do objeto B (acionamento) porque o objeto B é do sistema (UITableView). Nós não controlamos esse objeto. Não é nosso.

Não fazemos o acionamento, apenas a escuta.

Então quando usamos algum delegate do sistema, como o UITextFieldDelegate, nós apenas fazemos código no objeto A.

E uma closure que o iOS usa, tem exemplo? Sim, vamos lá.

image

Nesse caso, o handler é uma closure do sistema. No handler devemos definir a ação que o código executará ao clicarmos no botão. Podemos fazer isso de duas maneiras:

1 - Escrevendo o código da ação dentro do handler

2 - Criando uma função que tenha como parâmetro UIAlertAction descrevendo a ação e chamando-a dentro do handler.

E, por fim, quando usar Closure e quando usar Delegate?

A resposta, como sempre, é: depende! rs

No geral, é recomendável dar preferência para delegates, pois eles facilitam a realização de testes unitários.

No entanto, faz mais sentido usar Closures para ações mais pontuais no código, ou seja, quando você quer passar uma informação específica do objeto B para o objeto A em um momento. Já delegate será utilizado em situações em que várias informações, em diferentes momentos, serão passadas do objeto B para o A.

Compartilhe
Comentários (1)
Luis Pereira
Luis Pereira - 29/09/2023 16:22

Show!!!! muito bem