Swift – ¿Cómo iterar sobre custom types?
Hoy aprenderemos sobre los protocolos SequenceType y GeneratorType. Veremos como estos nos ayudan a conformar una interfaz para que nuestros propios tipos de datos sea iterables a través de un bucle for-in.
Creo que resulta evidente la utilidad que esto tiene y si alguien piensa que por defecto lo podemos hacer pues está completamente equivocado y lo vamos a demostrar. Hoy aprenderemos a implementar esta funcionalidad.
Tomemos como ejemplo una Pila y veamos si podemos iterar sobre cada elemento almacenado:
import Cocoa struct Stack<Element> { var items = [Element]() mutating func push(newItem: Element) { items.append(newItem) } // push mutating func pop() -> Element? { guard !items.isEmpty else { return nil } // guard return items.removeLast() } // pop mutating func append(item: Element) { self.push(item) } // append func map<U>(f: Element -> U) -> Stack<U> { var mappedItems = [U]() for item in items { mappedItems.append(f(item)) } // for return Stack<U>(items: mappedItems) } // map var count: Int { return items.count } // count subscript(i: Int) -> Element { return items[i] } // subscript } // Stack var myStack = Stack<Int>() myStack.push(10) myStack.push(20) myStack.push(30) for value in myStack.items { print("Obteniendo el valor: (value)") } // for
y la salida en pantalla sería:
Obteniendo el valor: 10 Obteniendo el valor: 20 Obteniendo el valor: 30
Sí, funciona perfectamente.
¿No debería de haber fallado?
pues no, hemos hecho trampa, realmente no estamos iterando sobre nuestro tipo Stack, lo hacemos sobre el arreglo llamado items que es miembro de la estructura Stack, y que al ser un arreglo común, Swift conoce como iterar sobre él.
Para poder usar nuestro propio tipo de dato y que el bucle for-in por ejemplo, sepa de donde extraer los valores, tenemos que adoptar dos protocolos que son los que establecen la interfaz necesaria para que esto ocurra.
El protocolo GeneratorType
Comprendamos antes la intención detrás de este protocolo que forma parte de la librería estándar de Swift y que a su vez cuenta con un tipo asociado, algo que ya analizamos en el artículo sobre tipos genéricos.
Su definición es bastante sencilla:
protocol GeneratorType { associatedtype Element mutating func next() -> Element? } // GeneratorType
aquí tenemos un tipo asociado nombrado Element y una función marcada como mutable de nombre next y que devuelve un valor opcional de tipo Element.
Creemos ahora una estructura de nombre StackGenerator que implemente el protocolo GeneratorType y que obtendrá los valores a generar de los elementos almacenados dentro de la Pila:
protocol GeneratorType { associatedtype Element mutating func next() -> Element? } // GeneratorType struct StackGenerator<T>: GeneratorType { typealias Element = T var stack: Stack<T> mutating func next() -> Element? { return stack.pop() } // next } // StackGenerator var myStack = Stack<Int>() myStack.push(10) myStack.push(20) myStack.push(30) var myStackGenerator = StackGenerator(stack: myStack) while let value = myStackGenerator.next() { print("Obteniendo el valor: (value)") } // while
la salida en pantalla sería:
Obteniendo el valor: 30 Obteniendo el valor: 20 Obteniendo el valor: 10
La función de GeneratorType es que podamos llamar al método next repetidas veces y que en cada una de ellas nos genere un nuevo valor. En caso de que no sea posible seguir generando más valores el método devuelve nil.
El protocolo SequenceType
El próximo protocolo que analizaremos será SequenceType que forma parte también de la librería estándar del lenguaje y cuenta con la siguiente definición:
protocol SequenceType { associatedtype Generator: GeneratorType func generate() -> Generator } // SequenceType
como podemos observar es igual de sencilla que el anterior protocolo. Se declara un tipo asociado de nombre Generator y de tipo GeneratorType y un método de nombre generate que devuelve un objeto Generator.
Ahora en pos de entender mejor su funcionamiento unamos el código que ya tenemos con este nuevo protocolo y hagamos que nuestra Pila lo adopte:
//: Playground - noun: a place where people can play import Cocoa struct StackGenerator<T>: GeneratorType { typealias Element = T var stack: Stack<T> mutating func next() -> Element? { return stack.pop() } // next } // StackGenerator struct Stack<Element>: SequenceType { var items = [Element]() mutating func push(newItem: Element) { items.append(newItem) } // push mutating func pop() -> Element? { guard !items.isEmpty else { return nil } // guard return items.removeLast() } // pop mutating func append(item: Element) { self.push(item) } // append func map<U>(f: Element -> U) -> Stack<U> { var mappedItems = [U]() for item in items { mappedItems.append(f(item)) } // for return Stack<U>(items: mappedItems) } // map func generate() -> StackGenerator<Element> { return StackGenerator(stack: self) } // generate var count: Int { return items.count } // count subscript(i: Int) -> Element { return items[i] } // subscript } // Stack var myStack = Stack<Int>() myStack.push(10) myStack.push(20) myStack.push(30) for value in myStack { print("Getting the value: (value)") } // for
la salida en pantalla sería:
Getting the value: 30 Getting the value: 20 Getting the value: 10
De la línea 61 a la 65 implementamos el método generate el cual se apoya en la estructura StackGenerator y esta a su vez es la que retornamos como objeto de tipo GeneratorType.
En este punto ya queda bastante claro el comportamiento: el método generate le brinda al bucle for-in un flujo de valores sobre los cuales iterar y esto lo obtiene gracias al objeto de tipo GeneratorType que este método retorna y que haciendo uso de su método next se va obteniendo valor tras valor.
Dicho esto ya podemos confiar nuestra Pila al bucle for-in tal y como podemos constatar en la línea 87, sin necesidad de hacer referencias a nada más que a la instancia del objeto.
El protocolo SequenceType junto a GeneratorType conforman una interfaz para el bucle for-in, logrando así que los valores que conforman cierta colección de datos puedan ser recorridos / iterados.
Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo y síguenos en nuestras redes sociales. Mantente al tanto de todas nuestras publicaciones.
Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.
Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.
¡Cualquier duda o sugerencia, ya sea errores a corregir o ejemplos a añadir, será más que bienvenida, necesaria!