Swift Genéricos

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

La biblioteca estándar de Swift está construida con código genérico.

Los tipos Array y Dictionary de Swift son colecciones genéricas.

Puedes crear un array de Int, o un array de String, o incluso un array de datos de cualquier otro tipo de Swift.

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

Ejemplo

// Define 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:

交换前数据: 100 和 200
交换后数据: 200 和 100

El ejemplo anterior solo sirve para intercambiar variables de tipo entero Int. Si quieres intercambiar dos valores String o Double, tienes que reescribir una función correspondiente, por ejemplo 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, el código funcional es el mismo, solo difiere en el tipo. En este caso podemos usar genéricos para evitar escribir código repetido.

Los genéricos utilizan un nombre de tipo placeholder (representado aquí por la letra T) en lugar del nombre de tipo real (por ejemplo, Int, String o Double).

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

swapTwoValues va seguido del nombre de tipo placeholder (T), encerrado entre corchetes angulares (<T>). Estos corchetes angulares le indican a Swift que T es un nombre de tipo placeholder dentro de la definición de la función swapTwoValues(_:_:), por lo tanto Swift no buscará un tipo real llamado T.

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

Ejemplo

// Define 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:

交换前数据:  100 和 200
交换后数据: 200 和 100
交换前数据:  A 和 B
交换后数据: B 和 A

Tipos genéricos

Swift te permite definir tus propios tipos genéricos.

Las clases, estructuras y enumeraciones personalizadas pueden trabajar con cualquier tipo, al igual que Array y Dictionary.

A continuación, escribiremos un tipo de colección genérico llamado Stack (pila), que solo permite agregar nuevos elementos al final de la colección (llamado apilar) y también solo permite eliminar elementos desde el final (llamado desapilar).

La imagen se explica de izquierda a derecha de la siguiente manera:

  • Hay tres valores en la pila.
  • Un cuarto valor se empuja a la cima de la pila.
  • Ahora hay cuatro valores en la pila, el que se apiló más recientemente está en la cima.
  • El valor más alto de la pila se elimina, o se denomina desapilar.
  • Después de eliminar un valor, ahora la pila vuelve a tener solo tres valores.

El siguiente ejemplo es una versión no genérica de la pila, tomando como ejemplo una 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 una propiedad Array llamada items para almacenar valores en la pila. Stack proporciona dos métodos: push(_:) y pop(), que se usan para insertar valores en la pila y eliminarlos de la pila. Estos métodos están marcados como mutating porque necesitan modificar el array items de la estructura.

La estructura IntStack anterior solo se puede usar con el tipo Int. Sin embargo, se puede definir una estructura Stack genérica para poder manejar valores de cualquier tipo.

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("Elementos de cadena apilados:") stackOfStrings.push("google") stackOfStrings.push("ejemplo") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("Elemento desapilado:" + deletetos) var stackOfInts = Stack<Int>() print("Elementos enteros apilados:") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);

El resultado de la ejecución del ejemplo es:

字符串元素入栈: 
["google", "ejemplo"]
出栈元素: ejemplo
整数元素入栈: 
[1, 2]

Stack es básicamente igual a IntStack, con el parámetro de tipo placeholder Element reemplazando al tipo real Int.

En el ejemplo anterior, Element se usa como marcador de posición en los siguientes tres lugares:

  • Crearitemsla propiedad, usandoElementun array vacío de tipo Element para inicializarla.
  • Especificarpush(_:)el único parámetro del métodoitemdebe ser de tipoElementtipo.
  • Especificarpop()el tipo de retorno del método debe serElementtipo.

Extensión de tipos genéricos

Cuando extiendes un tipo genérico (usando la palabra clave extension), no necesitas proporcionar una lista de parámetros de tipo en la definición de la extensión. Más convenientemente, la lista de parámetros de tipo declarada en la definición del tipo original está disponible en la extensión, y estos nombres de parámetros del tipo original se utilizan como referencias a los parámetros de tipo de la definición original.

El siguiente ejemplo extiende el tipo genérico Stack y le agrega una propiedad computada de solo lectura llamada topItem, que devolverá el elemento en la cima actual de la pila sin eliminarlo de la pila:

Genéricos

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

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

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

字符串元素入栈: 
栈中的顶部元素是:ejemplo.
["google", "ejemplo"]

También podemos especificar un tipo asociado extendiendo un tipo existente.

Por ejemplo, el tipo Array de Swift ya proporciona el método append(_:), una propiedad count y un subíndice que acepta un valor de índice de tipo Int para acceder a sus elementos. Estas tres funcionalidades cumplen con los requisitos del protocolo Container, por lo que solo necesitas declarar que Array adopta ese protocolo para extender Array.

El siguiente ejemplo simplemente crea una extensión vacía:

extension Array: Container {}

Restricciones de tipo

Una restricción de tipo especifica que un parámetro de tipo debe heredar de una clase específica, o cumplir con un protocolo específico o una composición de protocolos.

Sintaxis de restricción de tipo

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

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

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 una 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

Genéricos

// Función no genérica, busca el índice de la cadena especificada en el array func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // Si lo encuentra, devuelve el valor del índice return index } } return nil } let strings = ["google", "weibo", "taobao", "ejemplo", "facebook"] if let foundIndex = findIndex(ofString: "ejemplo", in: strings) { print("El índice de ejemplo es\(foundIndex)") }

El índice comienza desde 0.

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

ejemplo 的索引为 3

Tipos asociados

En Swift se usa la palabra clave associatedtype para definir un tipo asociado.

El siguiente ejemplo define un protocolo Container, que define un tipo asociado ItemType.

El protocolo Container solo especifica tres funcionalidades que cualquier tipo que cumpla con el protocolo Container debe proporcionar. Los tipos que cumplen con el protocolo también pueden proporcionar otras funcionalidades adicionales si satisfacen estas tres condiciones.

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // 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("ejemplo")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

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

["google", "ejemplo", "taobao"]
3

Sentencia where

Las restricciones de tipo pueden asegurar que un tipo cumpla con las restricciones definidas por una función o clase genérica.

Puedes definir restricciones para los parámetros en la lista de parámetros mediante una sentencia where.

Puedes escribir una sentencia where inmediatamente después de la lista de parámetros de tipo. La sentencia where va seguida de una o más restricciones sobre los tipos asociados, y/o una o más relaciones de igualdad entre tipos y tipos asociados.

Ejemplo

El siguiente ejemplo define una función genérica llamada allItemsMatch, que se usa para verificar si dos instancias de Container contienen los mismos elementos en el mismo orden.

Si todos los elementos coinciden, devuelve true; de lo contrario, devuelve false.

Genéricos

// Protocolo Container protocol Container { associatedtype ItemType // Agregar un nuevo elemento al contenedor mutating func append(_ item: ItemType) // Obtener el número de elementos del contenedor var count: Int { get } // Recuperar cada elemento del contenedor mediante un subíndice con tipo de índice Int subscript(i: Int) -> ItemType { get } } // // Tipo genérico TOS que se ajusta al protocolo Container struct Stack<Element>: Container { // Implementación original de Stack<Element> var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // 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 para usar Array como Container extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // Comprueba si dos contenedores tienen el mismo número de elementos if someContainer.count != anotherContainer.count { return false } // Comprueba si cada par de elementos es igual for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // Si todos los elementos coinciden, devuelve true return true } var tos = Stack<String>() tos.push("google") tos.push("ejemplo") tos.push("taobao") var aos = ["google", "ejemplo", "taobao"] if allItemsMatch(tos, aos) { print("Todos los elementos coinciden") } else { print("Los elementos no coinciden") }

El resultado de ejecución del programa anterior es:

Coincidir con todos los elementos
Otras extensiones