Boa tarde amigos Especialmente para os alunos do curso
"iOS Developer. Curso Avançado ”, preparamos uma tradução da segunda parte do artigo“ O Poder dos Genéricos em Swift ”.

Tipos relacionados, onde cláusulas, subscritos e mais ...No artigo
“O poder dos genéricos no Swift. A Parte 1 descreveu funções genéricas, tipos genéricos e restrições de tipo. Se você é iniciante, recomendo que você leia a primeira parte para entender melhor.
Ao definir um protocolo, às vezes é útil declarar um ou mais tipos relacionados como parte da definição. O tipo associado especifica o nome do stub para o tipo usado como parte do protocolo. O tipo real usado para esse tipo relacionado não será especificado até que o protocolo seja usado. Os tipos associados são declarados usando a palavra-chave
associatedtype
.
Podemos definir o protocolo para a pilha que criamos na
primeira parte .
protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } }
O protocolo
Stackable
define a funcionalidade necessária que qualquer pilha deve fornecer.
Qualquer tipo que esteja em conformidade com o protocolo
Stackable
deve poder especificar o tipo de valor que ele armazena. Ele deve garantir que apenas elementos do tipo correto sejam adicionados à pilha e deve ficar claro que tipo de elementos são retornados por seu subscrito.
Vamos mudar nossa pilha de acordo com o protocolo:
enum StackError: Error { case Empty(message: String) } protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } public struct Stack<T>: Stackable { public typealias Element = T var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } func count() -> Int { return array.count } } extension Stack: Collection { public func makeIterator() -> AnyIterator<T> { var curr = self return AnyIterator { curr.pop() } } public subscript(i: Int) -> T { return array[i] } public var startIndex: Int { return array.startIndex } public var endIndex: Int { return array.endIndex } public func index(after i: Int) -> Int { return array.index(after: i) } } extension Stack: CustomStringConvertible { public var description: String { let header = "***Stack Operations start*** " let footer = " ***Stack Operation end***" let elements = array.map{ "\($0)" }.joined(separator: "\n") return header + elements + footer } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.pop() stack.push(element: 3) stack.push(element: 4) print(stack)
Estendendo um tipo existente para indicar um tipo relacionado
Você pode estender um tipo existente para estar em conformidade com o protocolo.
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } extension Array: Container {}
Incluindo restrições no tipo associado:
Você pode adicionar restrições ao tipo associado no protocolo para garantir que os tipos relacionados cumpram essas restrições.
Vamos mudar o protocolo
Stackable
.
protocol Stackable { associatedtype Element: Equatable mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } }
Agora o tipo do elemento da pilha deve corresponder a
Equatable
, caso contrário,
Equatable
um erro em tempo de compilação.
Restrições de protocolo recursivas:
O protocolo pode fazer parte de seus próprios requisitos.
protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix }
Suffix
tem duas limitações: ele deve estar em conformidade com o protocolo
SuffixableContainer
(o protocolo é definido aqui) e seu tipo de
Item
deve corresponder ao tipo de contêiner do
Item
.
Há um bom exemplo na biblioteca padrão Swift na
Protocol Sequence
ilustrar este tópico.
Proposta sobre as limitações do protocolo recursivo:
https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.mdExtensões de tipo genérico:
Quando você estende um tipo genérico, não está descrevendo uma lista de parâmetros de tipo ao definir uma extensão. Em vez disso, uma lista de parâmetros de tipo da definição de origem está disponível no corpo da extensão e os nomes de parâmetros do tipo de fonte são usados para se referir aos parâmetros de tipo da definição de origem.
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } }
Cláusula where genérica
Para tipos relacionados, é útil definir requisitos. O requisito é descrito pela
cláusula where genérica . A cláusula
where
genérica permite exigir que o tipo associado esteja em conformidade com um protocolo específico ou que determinados parâmetros e tipos relacionados sejam os mesmos. A cláusula
where
genérica começa com a palavra-chave
where
, seguida por restrições para tipos relacionados ou pelo relacionamento de igualdade entre tipos e tipos relacionados. A cláusula Where genérica é escrita antes do colchete de abertura do tipo ou corpo da função.
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { }
Extensões com condições genéricas em que
Você pode usar a cláusula
where
genérica como parte da extensão. O exemplo a seguir estende a estrutura geral da
Stack
dos exemplos anteriores, adicionando o
isTop (_ :)
.
extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } }
A extensão adiciona o
isTop (_ :)
apenas quando itens na pilha suportam Equatable. Você também pode usar a cláusula
where
genérica com extensões de protocolo. Você pode adicionar vários requisitos à
where
separando-os com uma vírgula.
Tipos associados à cláusula Generic em que:
Você pode incluir a cláusula
where
genérica no tipo associado.
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator }
Para um protocolo que herda de outro protocolo, adicione uma restrição ao tipo de ligação herdada, incluindo a cláusula
where
genérica na declaração do protocolo. Por exemplo, o código a seguir declara um protocolo
ComparableContainer
que requer que o
Item
Comparable
suporte
Item
Comparable
:
protocol ComparableContainer: Container where Item: Comparable { }
Aliases de tipo genérico:
O alias de tipo pode ter parâmetros comuns. Ainda permanecerá um alias (ou seja, não introduzirá um novo tipo).
typealias StringDictionary<Value> = Dictionary<String, Value> var d1 = StringDictionary<Int>() var d2: Dictionary<String, Int> = d1
Nesse mecanismo, restrições adicionais aos parâmetros de tipo não podem ser usadas.
Esse código não funcionará:
typealias ComparableArray<T where T : Comparable> = Array<T>
Sobrescritos genéricos
Os subscritos podem usar o mecanismo genérico e incluir a cláusula
where
genérica. Você escreve o nome do tipo entre colchetes após o
subscript
e escreve a cláusula
where
genérica imediatamente antes da chave de abertura do corpo do subscrito.
extension Container { subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } }
Especialização genérica
Especialização genérica significa que o compilador clona um tipo ou função genérica, como Stack
<
T
>
, para um tipo específico de parâmetro, como Int. Essa função especializada pode ser otimizada especificamente para Int, enquanto tudo o que é supérfluo será removido. O processo de substituição de parâmetros de tipo por argumentos de tipo em tempo de compilação é chamado de
especialização .
Ao especializar a função genérica para esses tipos, podemos eliminar os custos de despacho virtual, chamadas em linha, quando necessário, e eliminar a sobrecarga do sistema genérico.
Sobrecarga do operador
Tipos genéricos não funcionam com operadores por padrão, para isso você precisa de um protocolo.
func ==<T: Equatable>(lhs: Matrix<T>, rhs: Matrix<T>) -> Bool { return lhs.array == rhs.array }
Uma coisa interessante sobre genéricos
Por que você não pode definir uma propriedade armazenada
estática para um tipo genérico?
Isso exigirá um armazenamento separado de propriedades para cada especialização genérica individual (T).
Recursos para estudo aprofundado:
https://github.com/apple/swift/blob/master/docs/Generics.rsthttps://github.com/apple/swift/blob/master/docs/GenericsManifesto.mdhttps://developer.apple.com/videos/play/wwdc2018/406/https://www.youtube.com/watch?v=ctS8FzqcRughttps://medium.com/@vhart/protocols-generics-and-existential-containers-wait-what-e2e698262ab1
Só isso. Vejo você no
curso .