English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Genericos no Swift

O Swift oferece genéricos para escrever funções e tipos flexíveis e reutilizáveis.

A biblioteca padrão do Swift é construída com código genérico.

Os tipos de array e dicionário do Swift são conjuntos genéricos.

Você pode criar um array de Int, um array de String, ou até mesmo um array de qualquer outro tipo de dados do Swift.

O exemplo a seguir é uma função não genérica exchange usada para trocar dois valores Int:

exemplo online

// Definir uma função para trocar dois valores
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var \&numb1 = 100
var \&numb2 = 200
 
print("Dados antes da troca: \(numb1) e \(numb2)")
swapTwoInts(&numb1, \&numb2)
print("Dados após a troca: \(numb1) e \(numb2)")

O resultado de execução do programa acima é:

Dados antes da troca: 100 e 200
Dados após a troca: 200 e 100

Os exemplos acima só são válidos para troca de variáveis do tipo Int. Se você quiser trocar dois valores String ou Double, precisará escrever uma função correspondente, como swapTwoStrings(_:_:) e swapTwoDoubles(_:_:], conforme mostrado a seguir:

Função de troca de valores String e Double

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

A partir do código acima, podemos ver que o código funcional é o mesmo, apenas os tipos são diferentes. Neste caso, podemos usar genéricos para evitar a repetição de código.

Os genéricos usam nomes de tipos placeholders (usamos a letra T aqui) para substituir nomes de tipos reais (por exemplo, Int, String ou Double).

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues seguido pelo nome de tipo placeholder (T) e colocado entre chaves (<T>)。Esses colchetes apontam para que o T seja um nome de tipo placeholder dentro da definição da função swapTwoValues(_:_:), então o Swift não busca um tipo real chamado T.

O seguinte exemplo é uma função genérica exchange usada para trocar valores Int e String:

exemplo online

// Definir uma função para trocar dois valores
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var \&numb1 = 100
var \&numb2 = 200
 
print("Dados antes da troca: \(numb1) e \(numb2)")
swapTwoValues(\&numb1, \&numb2)
print("Dados após a troca: \(numb1) e \(numb2)")
 
var \&str1 = \
var \&str2 = \
 
print("Dados antes da troca: \(str1) e \(str2)")
swapTwoValues(\&str1, \&str2)
print("Dados após a troca: \(str1) e \(str2)")

O resultado de execução do programa acima é:

Dados antes da troca:  100 e 200
Dados após a troca: 200 e 100
Dados antes da troca: A e B
Dados após a troca: B e A

Tipos genéricos

Swift permite que você defina seus próprios tipos genéricos.

Classe personalizada, estrutura e enumeração funcionam com qualquer tipo, como o uso de Array e Dictionary.

A seguir, vamos escrever um tipo de coleção genérica chamado Stack (pilha), que permite adicionar novos elementos apenas no extremo da coleção (chamado empilhar), e também pode remover elementos apenas do extremo (chamado desempilhar).

A análise da imagem, de esquerda para direita, é a seguinte:

  • Existem três valores na pilha.

  • O quarto valor é empilhado na parte superior da pilha.

  • Agora há quatro valores na pilha, o valor mais recente empilhado está na parte superior.

  • O valor mais recente da pilha é removido, ou chamado de desempilhar.

  • Depois de remover um valor, a pilha agora tem apenas três valores.

A seguir está um exemplo de pilha não genérica, usando pilha de tipo Int como exemplo:

Pilha de tipo Int

struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

Esta estrutura usa uma propriedade Array chamada items para armazenar valores. A Stack fornece dois métodos: push(_) e pop(), usados para empilhar valores e remover valores da pilha. Esses métodos são marcados como mutating, porque precisam modificar o array items da estrutura.

A estrutura IntStack acima pode ser usada apenas para tipos Int. No entanto, pode-se definir uma estrutura Stack genérica para poder manipular valores de qualquer tipo.

Aqui está a versão genérica do mesmo código:

Pilha genérica

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
 
var stackOfStrings = Stack<String>()
print("Elemento de string inserido na pilha:")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
print(stackOfStrings.items);
 
let deletetos = stackOfStrings.pop()
print("Elemento removido: " + deletetos)
 
var stackOfInts = Stack<Int>()
print("Inserir elementos inteiros na pilha: ")
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);

O resultado da execução do exemplo é:

Inserindo elementos de string na pilha: 
["google", "w3codebox"]
Elemento removido: w3codebox
Inserir elementos inteiros na pilha: 
[1, 2]

A pilha Stack é basicamente a mesma que a IntStack, o parâmetro de tipo de suporte Element substitui o tipo Int real.

No exemplo acima, o Element é usado como suporte em três lugares:

  • Criar items Propriedade, usar Element Um array vazio do tipo para inicializá-lo.

  • Especificar push(_:) O parâmetro único do método item O tipo deve ser Element Tipos.

  • Especificar pop() O tipo de retorno do método deve ser Element Tipos.

Estender tipos genéricos

Quando você estende um tipo genérico (usando a palavra-chave extension), você não precisa fornecer uma lista de parâmetros de tipo na definição da extensão. É mais conveniente que a lista de parâmetros de tipo declarada na definição original do tipo possa ser usada na extensão, e os nomes dos parâmetros provenientes do tipo original serão usados como referências para os parâmetros de tipo na definição original.

A seguir, uma instância de extensão do tipo genérico Stack é adicionada, que inclui uma propriedade de leitura calculada chamada topItem, que retornará o elemento no topo da pilha sem removê-lo da pilha:}}

genérico

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
 
extension Stack {
    var topItem: Element? {
       return items.isEmpty ? nil : items[items.count - 1]
    }
}
 
var stackOfStrings = Stack<String>()
print("Elemento de string inserido na pilha:")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
 
if let topItem = stackOfStrings.topItem {
    print("O elemento no topo da pilha é: \(topItem).")
}
 
print(stackOfStrings.items)

No exemplo, a propriedade topItem retornará um valor opcional do tipo Element. Quando a pilha está vazia, topItem retornará nil; quando a pilha não está vazia, topItem retornará o último elemento do array items.

O resultado de execução do programa acima é:

Inserindo elementos de string na pilha: 
O elemento no topo da pilha é: w3codebox.
["google", "w3codebox"]

Também podemos especificar tipos associados através da extensão de um tipo existente.

Por exemplo, o tipo Array do Swift já fornece o método append(_:], uma propriedade count e um índice que aceita valores do tipo Int para recuperar seus elementos. Essas três funcionalidades atendem aos requisitos do protocolo Container, então você pode simplesmente declarar que Array adota o protocolo para estender Array.

A seguir, crie uma extensão vazia:

extension Array: Container {}

Restrição de tipo

A restrição de tipo especifica um tipo de parâmetro que deve herdar de uma classe específica ou seguir um protocolo específico ou uma combinação de protocolos.

Sintaxe de restrição de tipo

Você pode escrever uma restrição de tipo após o nome de um parâmetro de tipo, separada por dois pontos, como parte da cadeia de parâmetros de tipo. A sintaxe básica dessa restrição de tipo aplicada a funções genéricas é mostrada a seguir (igual à sintaxe de tipos genéricos):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // Aqui está a parte do corpo da função genérica
}

Essa função tem dois parâmetros de tipo. O primeiro parâmetro T, que tem a restrição de que T deve ser um subtipo da classe SomeClass; o segundo parâmetro U, que tem a restrição de que U deve conformar ao protocolo SomeProtocol.

exemplo online

genérico

// Função não genérica, busca o índice da string especificada no array
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            // Retorna o valor do índice ao encontrar
            return index
        }
    }
    return nil
}
 
 
let strings = ["google", "weibo", "taobao", "w3codebox", "facebook"]
if let foundIndex = findIndex(ofString: "w3codebox", in: strings) {
    print("w3O índice do codebox é (foundIndex)")
}

O índice começa em 0.

O resultado de execução do programa acima é:

w3O índice do codebox é 3

Classe associada

No Swift, o keyword associatedtype é usado para definir tipos associados como exemplo.

Abaixo está uma definição de exemplo do protocolo Container, que define um tipo de associação ItemType.

O protocolo Container especifica apenas três funcionalidades que qualquer tipo que obedece ao protocolo Container deve fornecer. Os tipos que obedeem ao protocolo podem fornecer funcionalidades adicionais além dessas três condições.

// protocolo Container
protocolo Container {
    associatedtype ItemType
    // adicionar um novo elemento ao contêiner
    mutating func append(_ item: ItemType)
    // obter o número de elementos no contêiner
    var count: Int { get }
    // recuperar cada elemento do contêiner através do valor de índice de tipo Int
    subscript(i: Int) -> ItemType { get }
}
// A estrutura Stack obedece ao protocolo Container
struct Stack<Element>: Container {
    // parte original da implementação de Stack<Element>
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // implementação da parte do protocolo Container
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
var tos = Stack<String>()
tos.push("google")
tos.push("w3codebox)
tos.push("taobao")
// Lista de elementos
print(tos.items)
// Número de elementos
print( tos.count)

O resultado de execução do programa acima é:

["google", "w3codebox, "taobao"]
3

Cláusula Where

As restrições de tipo garantem que o tipo esteja em conformidade com as restrições de definição de função ou classe genérica.

Você pode definir restrições para os parâmetros através da cláusula where na lista de parâmetros.

Você pode escrever uma cláusula where, imediatamente após a lista de parâmetros de tipo, seguida de uma ou mais restrições para o tipo de associação, e (ou) uma ou mais relações de equivalência (igualdade) entre o tipo e a associação.

exemplo online

abaixo há um exemplo de definição de uma função genérica chamada allItemsMatch, usada para verificar se dois exemplos de Container contêm elementos da mesma ordem e idênticos.

se todos os elementos puderem coincidir, retornar true, caso contrário, retornar false.

genérico

// protocolo Container
protocolo Container {
    associatedtype ItemType
    // adicionar um novo elemento ao contêiner
    mutating func append(_ item: ItemType)
    // obter o número de elementos no contêiner
    var count: Int { get }
    // recuperar cada elemento do contêiner através do valor de índice de tipo Int
    subscript(i: Int) -> ItemType { get }
}
 
// // tipo genérico TOS que segue o protocolo Container
struct Stack<Element>: Container {
    // parte original da implementação de Stack<Element>
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // implementação da parte do protocolo Container
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
// extensão, usar Array como Container
extension Array: Container {}
 
func allItemsMatch<C>1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        
        // verificar se dois contêineres contêm o mesmo número de elementos
        se someContainer.count != anotherContainer.count {
            return false
        }
        
        // Verificar se cada par de elementos coincide
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // Todos os elementos coincidem, retorna true
        return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("w3codebox)
tos.push("taobao")
 
var aos = ["google", "w3codebox, "taobao"]
 
if allItemsMatch(tos, aos) {
    print("Coincidir com todos os elementos")
} else {
    print("Elemento não coincide")
}

O resultado de execução do programa acima é:

Coincidir com todos os elementos