O código genérico permite escrever funções e tipos flexíveis e reutilizáveis que podem funcionar com qualquer tipo, sujeito aos requisitos que você definir. Você pode escrever um código que evite duplicação e expresse sua intenção de maneira clara e abstrata. - Documentos rápidos
Todo mundo que escrevia no Swift usava genéricos. Array , Dictionary , Set - as opções mais básicas para usar genéricos da biblioteca padrão. Como eles são representados por dentro? Vamos ver como esse recurso fundamental da linguagem é implementado pelos engenheiros da Apple.
Os parâmetros genéricos podem ser limitados por protocolos ou não, embora, basicamente, os genéricos sejam usados em conjunto com protocolos que descrevem o que exatamente pode ser feito com parâmetros de método ou campos de tipo.
Para implementar genéricos, o Swift usa duas abordagens:
- Modo de tempo de execução - o código genérico é um invólucro (Boxe).
- Compiletime-way - o código genérico é convertido em um tipo específico de código para otimização (Especialização).
Boxe
Considere um método simples com um parâmetro genérico de protocolo ilimitado:
 func test<T>(value: T) -> T { let copy = value print(copy) return copy } 
O compilador rápido cria um único bloco de código que será chamado para trabalhar com qualquer <T> . Ou seja, independentemente de escrevermos test(value: 1) ou test(value: "Hello") , o mesmo código será chamado e informações adicionais sobre o tipo <T> contém todas as informações necessárias serão transferidas para o método .
Pouco pode ser feito com esses parâmetros de protocolo ilimitados, mas já para implementar esse método, você precisa saber como copiar um parâmetro, precisa saber o seu tamanho para alocar memória para ele em tempo de execução, precisa saber como destruí-lo quando o parâmetro sair do campo visibilidade. A Value Witness Table ( VWT ) é usada para armazenar essas informações. VWT é criado no estágio de compilação para todos os tipos e o compilador garante que, em tempo de execução, exista exatamente esse layout do objeto. Deixe-me lembrá-lo de que as estruturas no Swift são passadas por valor e as classes por referência; portanto, coisas diferentes serão feitas para let copy = value com T == MyClass e T == MyStruct .
Ou seja, chamar o método de test com a aprovação da estrutura declarada eventualmente se parecerá com isso:
 
As coisas ficam um pouco mais complicadas quando o MyStruct uma estrutura genérica e assume o formato MyStruct<T> . Dependendo do <T> dentro do MyStruct , os metadados e o VWT serão diferentes para os tipos MyStruct<Int> e MyStruct<Bool> . Esses são dois tipos diferentes em tempo de execução. Mas a criação de metadados para todas as combinações possíveis de MyStruct e T extremamente ineficiente, então o Swift segue o caminho contrário e, nesses casos, constrói metadados em tempo de execução em movimento. O compilador cria um padrão de metadados para a estrutura genérica, que pode ser combinada com um tipo específico e, como resultado, receber informações completas do tipo em tempo de execução com o VWT correto.
 
Quando combinamos informações, obtemos metadados com os quais podemos trabalhar (copiar, mover, destruir).
Ainda é um pouco mais complicado quando restrições de protocolo são adicionadas aos genéricos. Por exemplo, restringimos <T> protocolo Equatable . Que seja um método muito simples que compara os dois argumentos passados. O resultado é apenas um invólucro sobre o método de comparação.
 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } 
Para que o programa funcione corretamente, você deve ter um ponteiro para o método de comparação static func ==(lhs:T, rhs:T) . Como conseguir isso? Obviamente, a transmissão VWT não VWT suficiente, não contém essas informações. Para resolver esse problema, existe uma Protocol Witness Table ou PWT . Esse VWT é semelhante ao VWT e é criado no estágio de compilação de protocolos e descreve esses protocolos.
 isEquals(first: 1, second: 2)  
- Dois argumentos passados
- Passe metadados para Intpara poder copiar / mover / destruir objetos
- Passamos as informações que IntimplementaEquatable.
Se a restrição exigisse a implementação de outro protocolo, por exemplo, T: Equatable & MyProtocol , as informações sobre o MyProtocol seriam adicionadas com o seguinte parâmetro:
 isEquals(..., intIsEquatable: Equatable.witnessTable, intIsMyProtocol: MyProtocol.witnessTable) 
O uso de wrappers para implementar genéricos permite implementar de forma flexível todos os recursos necessários, mas possui uma sobrecarga que pode ser otimizada.
Especialização genérica
Para eliminar a necessidade desnecessária de obter informações durante a execução do programa, foi utilizada a chamada abordagem de especialização genérica. Permite substituir um wrapper genérico por um tipo específico por uma implementação específica. Por exemplo, para duas chamadas para isEquals(first: 1, second: 2) e isEquals(first: "Hello", second: "world") , além da implementação principal do "wrapper", duas versões adicionais completamente diferentes do método para Int e para String .
Código fonte
Primeiro, crie um arquivo generic.swift e escreva uma pequena função genérica que consideraremos.
 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } isEquals(first: 10, second: 11) 
Agora você precisa entender o que eventualmente se transforma em um compilador.
Isso pode ser visto claramente compilando nosso arquivo .swift no Swift Intermediate Language ou SIL .
Um pouco sobre o SIL e o processo de compilação
SIL é o resultado de um dos vários estágios da compilação rápida.
O código-fonte .swift é passado para o Lexer, que cria uma árvore de sintaxe abstrata ( AST ) do idioma, com base em que verificação de tipo e análise semântica do código são realizadas. O SilGen converte AST para SIL , chamado raw SIL , com base no qual o código é otimizado e um canonical SIL otimizado canonical SIL obtido, que é passado para o IRGen para conversão em IR - um formato especial que o LLVM entende, que será convertido em , . ` .o , . , . SIL`.
E novamente para os genéricos
Crie um arquivo SIL partir do nosso código-fonte.
 swiftc generic.swift -O -emit-sil -o generic-sil.s 
Temos um novo arquivo com a extensão *.s . Olhando para dentro, veremos um código muito menos legível que o original, mas ainda relativamente claro.
Encontre a linha com o comentário // isEquals<A>(first:second:) . Este é o começo da descrição do nosso método. Ele termina com um comentário // end sil function '$s4main8isEquals5first6secondSbx_xtSQRzlF' . Seu nome pode ser um pouco diferente. Vamos analisar um pouco a descrição do método.
- %0e- %1na linha 21 são o- firste o- secondparâmetros, respectivamente
- Na linha 24, obtemos informações de tipo e passamos para %4
- Na linha 25, obtemos um ponteiro para um método de comparação a partir de informações de tipo
- na linha 26 Chamamos o método por ponteiro, passando os parâmetros e as informações de tipo
- Na linha 27, damos o resultado.
Como resultado, vemos: para executar as ações necessárias na implementação do método genérico, precisamos obter informações da descrição do tipo <T> durante a execução do programa.
Prosseguimos diretamente para a especialização.
No arquivo SIL compilado, imediatamente após a declaração do método geral isEquals , isEquals a declaração do especialista para o tipo Int .
Na linha 39, em vez de obter o método em tempo de execução a partir das informações de tipo, o método para comparar números inteiros "cmp_eq_Int64" chamado imediatamente.
Para que o método seja "especializado", a otimização deve estar ativada . Você também precisa saber que
O otimizador só pode executar especialização se a definição da declaração genérica estiver visível no Módulo atual ( Origem )
Ou seja, o método não pode ser especializado entre diferentes módulos Swift (por exemplo, o método genérico da biblioteca Cocoapods). Uma exceção é a biblioteca Swift padrão, na qual tipos básicos como Array , Set e Dictionary . Todos os genéricos da biblioteca base são especializados em tipos específicos.
Nota: Os atributos @inlinable e @usableFromInline foram implementados no Swift 4.2, que permite ao otimizador ver os corpos dos métodos de outros módulos e parece que há uma oportunidade de especializá-los, mas esse comportamento não foi testado por mim ( fonte )
Referências
- Descrição dos genéricos
- Otimização no Swift
- Apresentação mais detalhada e aprofundada sobre o tema.
- Artigo em inglês