Swift e Objective-C

Dominando Swift resultBuilder: por que o Builder Pattern evoluiu para a linguagem

Você pensou nisso quando viu SwiftUI pela primeira vez?

4 min de leitura
Imagem de capa de Dominando Swift resultBuilder: por que o Builder Pattern evoluiu para a linguagem

Dominando Swift resultBuilder: como o Builder Pattern evoluiu para a sintaxe da linguagem

Você pensou nisso quando viu SwiftUI pela primeira vez?

“Espera, como listar várias views dentro de VStack { Text("A"); Text("B") } pode funcionar?”

À primeira vista, parece mágica. Não há ponto e vírgula, vírgula nem return, mas tudo é montado automaticamente.

A identidade dessa mágica é o Swift resultBuilder.

Em resumo, resultBuilder transforma o Builder Pattern que antes implementávamos manualmente como padrão de projeto em um recurso da linguagem, permitindo que o compilador escreva esse código por nós. Ele substitui o código repetitivo de montar objetos passo a passo por uma única sintaxe de bloco com chaves.


O que era mesmo o Builder Pattern?

Vamos relembrar rapidamente o Builder Pattern tradicional.

O Builder Pattern cria um objeto complexo adicionando suas partes uma a uma, em vez de construir tudo de uma vez.

let request = URLRequestBuilder()
    .setURL("https://naver.com")
    .setMethod("GET")
    .addHeader("Accept", "application/json")
    .build()

A montagem é feita encadeando métodos assim.

É fácil de ler, mas há desvantagens. Você precisa criar manualmente a classe Builder toda vez e, se esquecer build(), o objeto não é concluído.

Ou seja, o desenvolvedor precisava gerenciar diretamente a lógica de montagem.

O resultBuilder entrega exatamente essa responsabilidade ao compilador.


Como o resultBuilder funciona?

A ideia central é simples.

O compilador passa, um por um, os valores listados em um bloco de chaves para funções de regra predefinidas e os combina em um único resultado final.

Essas funções de regra são métodos estáticos como buildBlock.

Por exemplo, vamos criar um builder bem simples que reúne strings.

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: " ")
    }
}

@StringBuilder
func greeting() -> String {
    "Olá"
    "resultBuilder"
    "sou eu"
}
// Resultado: "Olá resultBuilder sou eu"

Dentro da função greeting(), apenas listamos três strings em três linhas, certo?

O compilador substituiu essas três linhas por uma chamada a buildBlock("안녕하세요", "resultBuilder", "입니다").

O momento em que a listagem do bloco vira uma chamada a buildBlock
O momento em que a listagem do bloco vira uma chamada a buildBlock

A sintaxe passou a escrever o código de montagem que antes fazíamos manualmente.


Como condicionais e loops são tratados?

Às vezes queremos usar if ou for dentro de um bloco, algo comum também no SwiftUI.

Para isso, o resultBuilder oferece métodos de regra adicionais.

Veja os principais métodos resumidos em uma tabela.

Método Quando é chamado?
buildBlock Ao combinar vários valores do bloco em um só
buildOptional Quando existe apenas if e não existe else
buildEither(first:) / buildEither(second:) Ao processar o ramo if-else
buildArray Ao reunir resultados repetidos de for
buildExpression Ao transformar primeiro a expressão de cada linha

Os métodos implementados determinam quais construções de sintaxe o builder permite.

Sem criar buildOptional, não é possível usar if dentro do bloco.

Por isso, o @ViewBuilder do SwiftUI implementa quase todos esses métodos. Assim, condicionais e loops podem ser usados naturalmente dentro dos blocos de views.

@ViewBuilder torna isso possível ao implementar quase todos os métodos de regra
@ViewBuilder torna isso possível ao implementar quase todos os métodos de regra

O que gostei e os cuidados depois de usar na prática

Já usei resultBuilder em pequenos geradores de HTML e na construção de dados de teste.

O melhor foi que o local da chamada fica declarativo e elegante. Como o processo de montagem não aparece, quem lê consegue se concentrar em “o que criar”.

Mas também há cuidados claros.

  1. As mensagens de erro de compilação costumam ser pouco úteis. Se os tipos não corresponderem dentro de um bloco, o erro pode apontar para um local totalmente errado.
  2. O uso excessivo pode piorar as coisas. Se você só precisa criar um array simples, é melhor usar um literal de array.
  3. Durante a depuração, é difícil rastrear qual método build... foi realmente chamado.

Por isso, recomendo usá-lo apenas com estruturas montadas repetidamente, como uma DSL (Domain-Specific Language, linguagem específica de domínio). SwiftUI, definições de rotas no servidor e query builders são bons exemplos.

Uma dica para o desenvolvimento iOS: na maioria dos casos, é mais importante entender e usar bem @ViewBuilder do que criar @resultBuilder diretamente.

Não apenas leia: digite o exemplo de hoje pelo menos uma vez
Não apenas leia: digite o exemplo de hoje pelo menos uma vez

Resumo

Em resumo, resultBuilder é uma evolução do Builder Pattern que a linguagem absorveu do código que antes escrevíamos manualmente.

Quando você entende que o compilador monta tudo o que é listado entre chaves, fica natural compreender por que o SwiftUI tem esse formato.

Digite o código do exemplo de hoje você mesmo. Ele é assimilado muito mais rápido do que apenas lendo.

Continue lendo