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

Generics en Swift

Swift proporciona genéricos para escribir funciones y tipos flexibles y reutilizables.

La biblioteca estándar de Swift se construye a través de código genérico.

Los tipos de array y diccionario en Swift son conjuntos genéricos.

Puedes crear un array de Int, también puedes crear un array de String, o incluso puede ser cualquier otro tipo de datos de array en Swift.

El siguiente ejemplo es una función no genérica exchange que se utiliza para intercambiar dos valores Int:

ejemplo en línea

// Definir una función para intercambiar dos variables
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("Datos antes del intercambio: (numb1) y (\(numb2)")
swapTwoInts(&numb1, \(numb2)
print("Datos después del intercambio: \(numb1) y (\(numb2)")

El resultado de la ejecución del programa anterior es:

Datos antes del intercambio: 100 y 200
Datos después del intercambio: 200 y 100

El ejemplo anterior solo se aplica al intercambio de variables de tipo Int. Si quieres intercambiar dos valores String o Double, debes escribir una función correspondiente, como swapTwoStrings(_:_:) y swapTwoDoubles(_:_:), como se muestra a continuación:

Función de intercambio de valores String y 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 del código anterior, podemos ver que el código funcional es el mismo, pero los tipos son diferentes; en este caso, podemos usar genéricos para evitar la repetición de código.

Los genéricos utilizan nombres de tipos de reemplazo (usando la letra T aquí como ejemplo) para reemplazar nombres de tipos reales (por ejemplo, Int, String o Double).

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

swapTwoValues seguido del nombre de tipo de reemplazo (T) entre paréntesis angulares (<T>)。Este corchete angular le indica a Swift que T es un nombre de tipo de reemplazo dentro de la definición de la función swapTwoValues(_:_:), por lo que Swift no buscará un tipo real llamado T.

El siguiente ejemplo es una función genérica exchange utilizada para intercambiar valores de Int y String:

ejemplo en línea

// Definir una función para intercambiar dos variables
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("Datos antes del intercambio: \(numb1) y (\(numb2)")
swapTwoValues(&numb1, \(numb2)
print("Datos después del intercambio: \(numb1) y (\(numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("Datos antes del intercambio: \(str1) y (\(str2)")
swapTwoValues(&str1, \(str2)
print("Datos después del intercambio: \(str1) y (\(str2)")

El resultado de la ejecución del programa anterior es:

Datos antes del intercambio:  100 y 200
Datos después del intercambio: 200 y 100
Datos antes del intercambio: A y B
Datos después del intercambio: B y A

Tipos genéricos

Swift te permite definir tus propios tipos genéricos.

Las clases personalizadas, estructuras y enumeraciones actúan sobre cualquier tipo, al igual que el uso de Array y Dictionary.

A continuación, procederemos a escribir un tipo de colección genérica llamado Stack (pila), que solo permite agregar nuevos elementos en el extremo de la colección (llamado apilar), y también solo se puede eliminar elementos del extremo (llamado desapilar).

La interpretación desde la izquierda a la derecha en la imagen es la siguiente:

  • Tres valores en la pila.

  • El cuarto valor se coloca en la parte superior de la pila.

  • Ahora hay cuatro valores en la pila, el valor más reciente colocado en la parte superior.

  • El valor más alto de la pila se elimina, o lo que es lo mismo, se desencola.

  • Después de eliminar un valor, la pila ahora solo tiene tres valores.

A continuación, se muestra un ejemplo de una versión no genérica de la pila, tomando como ejemplo la pila de tipo Int:

Pila de tipo Int

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

Esta estructura utiliza un atributo Array llamado items para almacenar valores. Stack proporciona dos métodos: push(_:) y pop(), que se utilizan para insertar valores en la pila y eliminar valores de la pila. Estos métodos están marcados como mutating, ya que necesitan modificar el array items de la estructura.

La estructura IntStack solo se puede usar para tipos Int. Sin embargo, se puede definir una estructura Stack genérica que pueda manejar cualquier tipo de valor.

A continuación, se muestra la versión genérica del mismo código:

Pila 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("Ingresar elemento de cadena en la pila:")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
print(stackOfStrings.items);
 
let deletetos = stackOfStrings.pop()
print("Elemento desencolado: " + deletetos)
 
var stackOfInts = Stack<Int>()
print("Elemento entero encolado: ")
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);

El resultado de la ejecución del ejemplo es:

Ingresar elemento de cadena en la pila: 
["google", "w3codebox"]
Elemento desencolado: w3codebox
Elemento entero encolado: 
[1, 2]

Stack es básicamente igual a IntStack, el parámetro de tipo de relleno Element reemplaza el tipo Int real.

En el ejemplo anterior, Element se utiliza como sustituto en los siguientes tres lugares:

  • Crear items Atributo, usar Element Un array vacío de ese tipo para inicializarlo.

  • Especificar push(_:) El único parámetro del método item El tipo debe ser Element Tipo.

  • Especificar pop() El tipo de retorno del método debe ser Element Tipo.

Expandir tipo genérico

Cuando se expande un tipo genérico (usando la palabra clave extension), no es necesario proporcionar una lista de parámetros de tipo en la definición de la extensión. Lo que es más conveniente es que la lista de parámetros de tipo declarada en la definición del tipo original puede ser utilizada en la extensión, y los nombres de los parámetros provenientes del tipo original se utilizarán como referencias de los parámetros de tipo en la definición original.

La siguiente instancia extiende el tipo genérico Stack, agregando una propiedad de solo lectura de tipo calculado llamada topItem, que regresará el elemento en la parte superior de la pila sin quitarlo de la pila:}}

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("Ingresar elemento de cadena en la pila:")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
 
if let topItem = stackOfStrings.topItem {
    print("El elemento superior en la pila es: \(topItem).")
}
 
print(stackOfStrings.items)

En el ejemplo, la propiedad topItem regresará un valor opcional de tipo Element. Cuando la pila está vacía, topItem regresará nil; cuando la pila no está vacía, topItem regresará el último elemento del array items.

El resultado de la ejecución del programa anterior es:

Ingresar elemento de cadena en la pila: 
El elemento superior en la pila es: w3codebox.
["google", "w3codebox"]

También podemos especificar tipos asociados a través de la extensión de un tipo existente.

Por ejemplo, el tipo Array de Swift ya proporciona el método append(_:], una propiedad count y un índice de acceso que acepta valores de tipo Int para recuperar sus elementos. Estas tres funcionalidades cumplen con los requisitos del protocolo Container, por lo que simplemente debes declarar que Array adopta el protocolo para extender Array.

A continuación, se muestra un ejemplo de cómo crear una extensión vacía:

extension Array: Container {}

Restricción de tipo

Una restricción de tipo especifica un tipo de parámetro que debe heredar de una clase específica o seguir un protocolo o una combinación de protocolos.

Sintaxis de restricción de tipo

Puedes escribir una restricción de tipo detrás de un nombre de parámetro de tipo, separada por dos puntos, como parte de una cadena de parámetros de tipo. La sintaxis básica de restricción de tipo que actúa sobre las funciones genéricas es la siguiente (igual que la sintaxis de tipos genéricos):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // Esta es la parte del cuerpo de la función genérica
}

Esta función tiene dos parámetros de tipo. El primer parámetro de tipo T, tiene una restricción de tipo que requiere que T sea un subclase de SomeClass; el segundo parámetro de tipo U, tiene una restricción de tipo que requiere que U cumpla con el protocolo SomeProtocol.

ejemplo en línea

genérico

// Función no genérica, encuentra el índice de una cadena específica en el array
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            // Si se encuentra, devuelve el valor de índice
            return index
        }
    }
    return nil
}
 
 
let strings = ["google", "weibo", "taobao", "w3codebox", "facebook"]
if let foundIndex = findIndex(ofString: "w3codebox", in: strings) {
    print("w3El índice de codebox es (foundIndex)")
}

El índice de subíndice comienza en 0.

El resultado de la ejecución del programa anterior es:

w3El índice de codebox es 3

Clase asociada

En Swift, se utiliza la palabra clave associatedtype para establecer ejemplos de tipos asociados.

A continuación se muestra un ejemplo de definición de un protocolo Container, que define un tipo asociado ItemType.

El protocolo Container solo especifica tres funciones que deben proporcionar todos los tipos que cumplen con el protocolo Container. Los tipos que cumplen con el protocolo pueden proporcionar otras funciones adicionales siempre que cumplan con estas tres condiciones.

// protocolo Container
protocolo Container {
    associatedtype ItemType
    // agregar un nuevo elemento al contenedor
    mutating func append(_ item: ItemType)
    // obtener el número de elementos en el contenedor
    var count: Int { get }
    // recuperar cada elemento del contenedor mediante un índice de tipo Int
    subscript(i: Int) -> ItemType { get }
}
// La estructura Stack sigue el protocolo Container
struct Stack<Element>: Container {
    // parte original de la implementación de Stack<Element>
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // parte de la implementación del 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)

El resultado de la ejecución del programa anterior es:

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

Declaración Where

Las restricciones de tipo pueden garantizar que el tipo cumpla con las restricciones de definición de la función o clase genérica.

Puedes definir restricciones sobre los parámetros a través de la declaración where en la lista de parámetros.

Puedes escribir una declaración where, seguida de la lista de parámetros de tipo, donde la declaración where sigue a una o más restricciones sobre el tipo asociado, y (o) una o más relaciones de equivalencia (igualdad) entre tipos y tipos asociados.

ejemplo en línea

a continuación, se define un ejemplo de función genérica llamada allItemsMatch, que se utiliza para verificar si dos ejemplos de Container contienen elementos de la misma secuencia y coincidencia.

si todos los elementos pueden coincidir, devolver true, de lo contrario devolver false.

genérico

// protocolo Container
protocolo Container {
    associatedtype ItemType
    // agregar un nuevo elemento al contenedor
    mutating func append(_ item: ItemType)
    // obtener el número de elementos en el contenedor
    var count: Int { get }
    // recuperar cada elemento del contenedor mediante un índice de tipo Int
    subscript(i: Int) -> ItemType { get }
}
 
// // tipo genérico TOS que sigue el protocolo Container
struct Stack<Element>: Container {
    // parte original de la implementación de Stack<Element>
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // parte de la implementación del protocolo Container
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
// extensión, usar Array como Container
extension Array: Container {}
 
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    donde C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        
        // verificar si dos contenedores contienen el mismo número de elementos
        si someContainer.count != anotherContainer.count {
            return false
        }
        
        // Verificar si cada par de elementos es equivalente
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // Todos los elementos coinciden, devuelve 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 con todos los elementos")
}
    print("Elemento no coincide")
}

El resultado de la ejecución del programa anterior es:

Coincidir con todos los elementos