Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Workshop iOS 4: Closures, generics & operators

390 views

Published on

Workshop iOS 4: Closures, generics & operators
- Generics
- Operadores
- Sorter

Presentado por nuestro ingeniero Alberto Irurueta

Published in: Mobile
  • Login to see the comments

  • Be the first to like this

Workshop iOS 4: Closures, generics & operators

  1. 1. WORKSHOP IOS: Closures, generics & operators Organizadores  Alberto Irurueta Carro Objetivos  Entender el funcionamiento de generics  Revisar el uso de Protocols y operadores con generics REPOSITORIO El repositorio del workshop lo podéis encontrar en: https://github.com/albertoirurueta/swift-protocols-and-generics-workshop Generics Generics permite implementar código en Swift que puede ser reutilizado con distintos tipos de datos (e incluso para tipos de datos que no estén definidos pero sigan algún tipo de protocolo). Por ejemplo, los arrays y diccionarios en Swift son structs que usan generics, de modo que podemos hacer instancias para contener cualquier tipo de datos. Para crear una clase que utilice generics, debe usarse la sintaxis <Tipo1, Tipo2, ...> donde Tipo1, Tipo2 son los tipos a los que nos referiremos dentro de una clase o función. Si queremos restringir que que los tipos genéricos sean una subclase de un tipo determinado, o implementen cierto protocolo puede usarse la sintaxis <Tipo : Protocolo>, <Tipo : Clase>
  2. 2. Ejemplo: Container.swift import Foundation /** Contenedor de un objeto cualquiera de tipo T. */ public class Container<T> { /** Valor interno almacenado por esta clase. */ private var _value: T /** Constructor. - parameter value: valor inicial proporcionado. */ public init(value: T) { _value = value } /** Obtiene o establece el valor interno de esta clase. */ public var value: T { get{ return _value } set { _value = newValue } } } En el código de este ejemplo, podemos crear instancias de Container para contener cualquier tipo de objeto. Swift infiere el tipo T a partir del objeto proporcionado en el constructor, tal y como se ve en el siguiente test: ContainerTests.swift ... func testConstructorAndValue() { let value1 = "hello" let container1 = Container<String>(value: value1) //comprobamos valor inicial XCTAssertEqual(value1, container1.value) //establecemos nuevo valor container1.value = "bye" //comprobamos
  3. 3. XCTAssertEqual(container1.value, "bye") let container2 = Container<Int>(value: 1) //valor inicial XCTAssertEqual(container2.value, 1) //nuevo valor container2.value = 10 //comprobamos XCTAssertEqual(container2.value, 10) } Operadores public protocol Equatable { public static func ==(lhs: Self, rhs: Self) -> Bool } public protocol Comparable : Equatable { public static func <(lhs: Self, rhs: Self) -> Bool public static func <=(lhs: Self, rhs: Self) -> Bool public static func >=(lhs: Self, rhs: Self) -> Bool public static func >(lhs: Self, rhs: Self) -> Bool } public func ><T : Comparable>(lhs: T, rhs: T) -> Bool public func <=<T : Comparable>(lhs: T, rhs: T) -> Bool public func >=<T : Comparable>(lhs: T, rhs: T) -> Bool Sorter En el repositorio hemos creado una serie de clases que utilizan generics, closures y protocols para ordenar cualquier tipo de datos. Se ha creado una clase base Sorter, y subclases con distintos algoritmos de ordenación implementados (StraightInsertationSorter, ShellSorter, QuicksortSorter, HeapsortSorter, FoundationSorter) En el repositorio está el código completo de estas clases, pero aquí os pongo algunos aspectos interesantes a considerar:  En Swift no existen las clases abstractas. O bien se utiliza un protocol, o bien usamos un constructor privado o internal para impedir la instanciación directa, como se ha hecho en el caso de Sorter. Sorter.swift /** Constructor. Es privado para impedir la instanciación directa de esta clase. */ internal init() { }
  4. 4.  Por comodidad en Sorter se han añadido métodos estáticos de factoría para crear instancias de subclases según el algoritmo de ordenación deseado. /** Método de factoría para crear instancias de Sorter que utilicen el método de ordenación indicado. - param method: método de ordenación. */ public static func create(method: SortingMethod) -> Sorter { switch method { case .foundation: return FoundationSorter() case .heapsort: return HeapsortSorter() case .quicksort: return QuicksortSorter() case .shell: return ShellSorter() case .straightInsertion: return StraightInsertionSorter() } } /** Método de factoría para crear instancias de Sorter con el método de ordenación por defecto. */ public static func create() -> Sorter { return create(method: DefaultSortingMethod) }  Errores: Hemos definido un enumerador que implementa Error para gestionar los errores que algunos métodos pueden lanzar en algunas situaciones /** Tipos de error que pueden obtenerse al ordenar arrays. */ public enum SorterError : Error { /** Si la posición fromIndex es mayor que la posición toIndex */ case illegalIndices /** Si se proporcionan valores de índices fuera del rango de índices accesibles por el array proporcionado (entre 0 y su count - 1). */ case indexOutOfBounds /** Si la ordenación falla por cualquier otro motivo. */ case sortingError }  Algunos métodos modifican los parámetros de entrada. Por defecto en Swift los parámetros de entrada se pasan por copia, lo cual, en el caso de los structs implica que si se modificasen, el
  5. 5. resultado no quedaría reflejado tras la ejecución de un método. Para ello puede indicarse que un parámetro es inout para pasar tanto clases como structs por referencia. public func sort<T>(_ array: inout [T], fromIndex from: Int, toIndex to: Int, comparator: Comparator) throws { try sort(&array, fromIndex: from, toIndex: to, comparator: { (o1, o2) -> Int in return comparator.compare(o1, o2) }) }  Para pasar un parámetro inout por referencia, dbee usarse &, como se ve en el ejemplo anterior  Si un método lanza algún tipo de excepción o ejecuta un método con try sin recoger su excepción, debe indicarse mediante throws en su declaración public override func sort<T>(_ array: inout [T], fromIndex: Int, toIndex: Int, comparator: (_ o1: T, _ o2: T) -> Int) throws { guard fromIndex <= toIndex else { throw SorterError.illegalIndices } guard fromIndex >= 0 && toIndex <= array.count else { throw SorterError.indexOutOfBounds } ...  Para llamar a un método que lanza excepciones, debe usarse try, try? o try!. Try nos permite relanzar una excepción o bien llamar al método dentro de un bloque do... catch para capturarla. try? nos permite obtener el resultado de un método que puede lanzar una excepción como un optional, u obtener nil si se produce la excepción, try! permite obtener el resultado de la ejecución del método asumiendo que éste nunca va a fallar (si se produjese una excepción se detendría la ejecución). public func sort<T : Comparable>(_ array: inout [T], fromIndex from: Int, toIndex to: Int) throws { try sort(&array, fromIndex: from, toIndex: to, comparator: { (o1, o2) -> Int in return Sorter.compare(o1, o2) }) } ... try! sorter.sort(&array, fromIndex: fromIndex, toIndex: toIndex)  Si el método devuelve void, también puede usarse do... catch{} pero se considera mala práctica no hacer nada en un bloque catch y además empeora la cobertura public func sort<T>(_ array: inout [T], comparator c: (_ o1: T, _ o2: T) -> Int) { do { try sort(&array, fromIndex: 0, toIndex: array.count, comparator: c) } catch { } }
  6. 6.  En el sorter hay métodos sort que usan closures, que usan Protocols, que usan generics de tipos Comparables. En el caso de las closures y los protocols, nos permiten definir cómo se realiza la comparación entre CUALQUIER tipo de datos, y permiten incluso realizar cosas como invertir el orden de ordenado. En el caso de generics con comparables, se nos simplifica el uso de la clase, ya que no es necesario proporcionar un comparador.  En un for puede indicarse un range para indicar los valores sobre los que se iteran Sin embargo, si el valor inicial es mayor que el final, el range no puede crearse y la ejecución falla. Si se necesitase crear un range en sentido decreciente, puede hacerse con la función stride (from: to: by:) o stride(from: through: by:). En el primer caso el valor final es exclusivo y en el segundo es inclusivo. En cualquier caso, recordad que un for siempre puede expresarse como un while junto con variables sobre las que se iteran.  En el caso de FoundationSorter se nos proporcionan parámetros o1 y o2 de tipo T, que no tienen por qué ser Comparables. Podemos forzar que T sea Comparable con una función auxiliar que defina un generic como T como Comparable y haciendo el cast dentro de un if para así poder usar los operadores < y > con un tipo T. public override func sort<T>(_ array: inout [T], fromIndex: Int, toIndex: Int, comparator: (_ o1: T, _ o2: T) -> Int) { array.sort { (o1, o2) -> Bool in FoundationSorter.compare(o1, o2, Int.self) } } private static func compare<T: Comparable>(_ o1: Any, _ o2: Any, _ type: T.Type) -> Bool{ if let c1 = o1 as? T, let c2 = o2 as? T { return c1 < c2 } else { return false } }  Para poder implementar Comparators que puedan usar los operadores < y > en SwiftSorter hemos definido las siguientes clases auxiliares donde se hace un cast de un tipo T cualquiera a un tipo U comparable class ComparatorAscendant<U : Comparable> : swiftProtocolsAndGenerics.Comparator { public func compare<T>(_ o1: T, _ o2: T) -> Int { if let c1 = o1 as? U, let c2 = o2 as? U { if c1 < c2 { return -1 } else if c1 == c2 { return 0 } else { return 1 } } else { return -1 } } }
  7. 7. class ComparatorDescendant<U : Comparable> : swiftProtocolsAndGenerics.Comparator { public func compare<T>(_ o1: T, _ o2: T) -> Int { if let c1 = o1 as? U, let c2 = o2 as? U { if c1 < c2 { return 1 } else if c1 == c2 { return 0 } else { return -1 } } else { return -1 } } } class ComparatorAndAveragerAscendant<U : Comparable> : swiftProtocolsAndGenerics.ComparatorAndAverager { public func compare<T>(_ o1: T, _ o2: T) -> Int { if let c1 = o1 as? U, let c2 = o2 as? U { if c1 < c2 { return -1 } else if c1 == c2 { return 0 } else { return 1 } } else { return -1 } } public func average<T>(_ o1: T, _ o2: T) -> T { if let i1 = o1 as? Int, let i2 = o2 as? Int { return ((i1 + i2) / 2) as! T } else { return 0 as! T } } }  También se han creado las clases auxiliares para el caso de Ints class IntComparatorAscendant : ComparatorAscendant<Int> { } class IntCompaaratorDescendant : ComparatorDescendant<Int> { } class IntComparatorAndAveragerAscendant : ComparatorAndAveragerAscendant<Int> { }

×