Design de software

Swift final: por que usá-lo em classes (design e desempenho)

Em uma revisão de código, você provavelmente já ouviu: “Adicione final a esta classe”.

4 min de leitura
Imagem de capa de Swift final: por que usá-lo em classes (design e desempenho)

Em uma revisão de código, você provavelmente já ouviu: “Adicione final a esta classe”.

Parece apenas um hábito, mas entender o motivo muda sua forma de enxergar o código.

Em resumo, final declara: “Esta classe não será estendida por herança”. É uma única linha que melhora desempenho e estabilidade do design.

Hoje vamos organizar os verdadeiros motivos para adicionar final às classes Swift.


Afinal, o que é final?

final é uma palavra-chave que impede herança e sobrescrita.

Quando aparece antes de uma classe, nenhuma outra classe pode herdá-la.

Antes de um método ou propriedade, também pode impedir a sobrescrita apenas daquele membro.

final class Logger {
    func log(_ msg: String) { print("[LOG] \(msg)") }
}

let logger = Logger()
logger.log("Pagamento concluído")   // Imprimir: [LOG] Pagamento concluído

Se você tentar herdar de Logger escrevendo class FileLogger: Logger, ocorrerá um erro de compilação.

É como cravar: “Esta classe termina aqui”.


Por que final deixa o código mais rápido?

O primeiro motivo é o desempenho. O ponto central é o modo de despacho (dispatch).

Enquanto a herança estiver aberta, o compilador não sabe até o runtime qual método será chamado e precisa procurá-lo em uma tabela toda vez.

Isso é o despacho dinâmico. Primeiro, a tabela de métodos (vtable) de cada classe é consultada; depois, a chamada é feita.

Com final, porém, a situação muda.

Como nenhuma subclasse pode interferir, o compilador pode ter certeza: “esta chamada sempre é para este método”.

Assim, ele chama diretamente, sem consultar a tabela. Esse é o despacho estático.

Indo além, métodos curtos podem ser expandidos no próprio ponto de chamada por meio de inlining.

O custo de uma chamada não é alto, mas, se o método for executado milhares de vezes dentro de um loop, a diferença se acumula.

Nesta bifurcação, uma chamada pode ter despacho estático ou dinâmico
Nesta bifurcação, uma chamada pode ter despacho estático ou dinâmico

O verdadeiro motivo é o “design”, mais do que o desempenho

Na verdade, considero este ponto mais importante que o desempenho.

É a capacidade de evitar o problema da classe base frágil (fragile base class).

Ao deixar a herança aberta, qualquer pessoa pode interferir livremente no funcionamento interno da classe pai que você criou.

Basta alterar sem querer um método do pai para que os filhos que o sobrescreveram quebrem em sequência.

A herança é a relação que mais viola a encapsulação. O filho passa a enxergar até as entranhas do pai.

Por isso, os princípios de design orientado a objetos dizem o seguinte.

Projete e documente para herança. Caso contrário, proíba-a. Esse é o famoso conselho de Effective Java.

final é a ferramenta para colocar esse conselho em prática no código.

O compilador impõe a intenção: “não projetei para herança, então não vou deixar aberto”.

Com a intenção clara, os colegas também se perdem menos ao ler o código no futuro.

Se não deve ficar aberto, use final: o compilador protege a intenção
Se não deve ficar aberto, use final: o compilador protege a intenção

Então é obrigatório adicionar sempre?

Um ponto importante: em Swift, um tipo por referência não precisa ser necessariamente uma classe.

Se o estado puder ser tratado como valor, struct costuma ser uma escolha melhor. struct não possui herança desde o início.

Antes de pensar “devo adicionar final?”, pergunte primeiro: “isso realmente precisa ser uma classe?”.

Se ainda assim precisar usar uma classe, avalie pelos critérios abaixo.

Situação Decisão
Classe sem intenção de herança Adicionar final (padrão)
Código com chamadas repetidas sensíveis a desempenho Usar final para induzir despacho estático
Framework com pontos de extensão claramente projetados Deixar a herança aberta
API que pressupõe herança, como UIViewController Não adicionar

É fácil lembrar assim.

  • Se a herança não foi planejada, adicione final por padrão
  • Abra apenas os pontos de extensão que você quiser oferecer
  • Se puder ser struct, evite uma classe desde o início

Em entrevistas, a pergunta costuma ser assim

P. Quais são as vantagens de adicionar a palavra-chave final?

Impede herança e sobrescrita, deixando clara a intenção do design. Ao mesmo tempo, o compilador pode usar despacho estático em vez de dinâmico, reduzindo o custo das chamadas e permitindo a otimização por inlining.

P. Então devemos adicionar final a todas as classes?

Classes projetadas e documentadas para herança devem permanecer abertas. Sem essa intenção, é mais seguro fechá-las por padrão; se o significado de valor fizer sentido, considere primeiro usar struct.


Uma única linha com final não é apenas uma dica de otimização: ela responde a “como esta classe deve ser usada?”.

A partir de hoje, ao criar uma classe, pergunte: “existe algum motivo para deixar a herança aberta?”. Esse hábito tornará seu código muito mais sólido.

Leia também