Design de software

Padrão Chain of Responsibility do Swift e a cadeia UIResponder

Ao criar um app iOS com Swift, você provavelmente já se perguntou por que este controlador de visualização recebe o evento ao tocar em um botão.

4 min de leitura
Imagem de capa de Padrão Chain of Responsibility do Swift e a cadeia UIResponder

Ao criar um app iOS com Swift, você provavelmente já se perguntou por que este controlador de visualização recebe o evento ao tocar em um botão.

Quando você rastreia por onde e como os eventos de toque percorrem a tela, encontra o padrão Chain of Responsibility na base.

Em resumo, a cadeia UIResponder é um exemplo representativo de como a Apple implementou, no nível do framework, o padrão Chain of Responsibility, um dos padrões de projeto GoF. Os responders passam a responsabilidade ao próximo responder até encontrar um objeto capaz de processar o evento.

Neste artigo, examino o código para explicar o que é Chain of Responsibility, como a cadeia UIResponder realmente funciona e como entendê-la ajuda no desenvolvimento prático.

Resumo dos pontos principais

  1. Chain of Responsibility é um padrão de projeto no qual vários objetos passam uma requisição como uma cadeia até encontrar um capaz de processá-la.
  2. A cadeia UIResponder do iOS implementa esse padrão no nível do framework.
  3. Um evento de toque sobe de UIView pelas superviews, pelo controlador de visualização, pela janela e pelo aplicativo.
  4. A propriedade next aponta para o próximo elo da cadeia; se ninguém processar o evento, ele será simplesmente descartado.

O que é o padrão Chain of Responsibility?

Chain of Responsibility é um padrão comportamental que desacopla o remetente de uma requisição do objeto que a processa.

É parecido com encaminhar uma aprovação em uma empresa. Se um assistente puder resolver, termina ali; caso contrário, passa ao gerente e, se ele também não puder, ao diretor.

Quem envia a requisição não precisa saber quem dará a aprovação final. Basta entregá-la ao primeiro elo, e a cadeia a encaminha automaticamente.

O ponto central é que cada objeto processador precisa saber apenas duas coisas: se consegue processar a requisição e quem é o próximo objeto caso não consiga.

Uma versão bem simples em Swift teria esta estrutura.

class Handler {
    var next: Handler?  // Próximo elo da cadeia
    func handle(_ request: Int) {
        // Passar a responsabilidade ao próximo objeto se não puder processar
        next?.handle(request)
    }
}

Essa estrutura, em que uma única propriedade next conecta a cadeia e o que não foi processado é passado para next, está incorporada diretamente ao UIResponder.


Como funciona a cadeia UIResponder?

No iOS, todo objeto que pode receber eventos de toque, movimento ou controle remoto herda de UIResponder. UIView, UIViewController, UIWindow e UIApplication são subclasses de UIResponder.

Por isso, cada um desses objetos possui uma propriedade cujo nome exato é next. Ela é definida em UIResponder.

// UIResponderPropriedade definida em
var next: UIResponder? { get }

Quando o usuário toca na tela, o sistema primeiro encontra a view mais interna que recebeu o toque. Isso é chamado de hit-testing e define a candidata a first responder.

Se essa view não processar o evento, o sistema segue next para cima. A ordem é aproximadamente esta.

A cadeia de responsabilidades que sobe por next
A cadeia de responsabilidades que sobe por next
  • UIView (a view tocada)
  • Suas superviews
  • O UIViewController que gerencia a view
  • UIWindow
  • UIApplication
  • UIApplicationDelegate

Se ninguém processar o evento depois que a cadeia for percorrida, ele será descartado silenciosamente. O app não trava nem gera erro; apenas ignora o evento.

O interessante é que next nem sempre é a superview.

Se a view for a view raiz de um controlador de visualização, next será o controlador, não uma superview.


Qual é a utilidade disso na prática?

Sinceramente, no começo o app funciona bem mesmo sem conhecer essa estrutura interna. Mas entendê-la muda claramente a forma de depurar.

Vou contar um caso que vivi: um gesto de toque não funcionava em uma view personalizada.

Ao rastrear em ordem onde o responder parava, encontrei rapidamente a causa: isUserInteractionEnabled estava false em uma superview, interrompendo a cadeia.

A propagação de eventos personalizados também é útil. Você pode definir mensagens que sobem pela cadeia UIResponder e entregar uma ação de uma view profunda ao controlador de visualização superior sem delegates.

endEditing(true), muito usado para ocultar o teclado, também utiliza essa cadeia. Ele encontra o first responder e chama resignFirstResponder.

Em resumo, entender a cadeia UIResponder permite rastrear logicamente onde um evento parou e projetar sua propagação para cima sem abusar de delegates.

O dia em que examinei a propriedade next no código
O dia em que examinei a propriedade next no código

Perguntas frequentes

P. first responder e a cadeia responder são coisas diferentes? R. first responder é um único objeto qualificado para receber um evento primeiro; a cadeia responder é toda a cadeia que sobe a partir dele.

P. next sempre aponta para uma superview? R. Não. Uma view comum aponta para sua superview, mas a view raiz de um controlador de visualização tem o controlador como next.

Acompanhei até qual responder este único toque sobe
Acompanhei até qual responder este único toque sobe

O nome Chain of Responsibility pode parecer difícil, mas tudo se resume a um princípio simples: se não puder processar, passe ao próximo. Espero que este artigo tenha ajudado você a entender a estrutura oculta do iOS chamada cadeia UIResponder. Quando encontrar um bug em que um evento não funciona, siga essa cadeia elo por elo.

Continue lendo