generic functions ios xcode generics swift

ios - functions - Cómo usar el protocolo genérico como tipo variable



generics ios (3)

Abordar su caso de uso actualizado:

(por cierto, Printable ya es un protocolo Swift estándar, por lo que probablemente desee elegir un nombre diferente para evitar confusiones)

Para imponer restricciones específicas a los implementadores de protocolos, puede restringir los tipos de protocolo. Entonces, para crear su colección de protocolos que requiere que los elementos sean imprimibles:

// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }

Ahora, si desea implementar una colección que solo puede contener elementos imprimibles:

struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }

Sin embargo, esto es probablemente de poca utilidad real, ya que no puede restringir las estructuras de colección Swift existentes como esa, solo las que implemente.

En su lugar, debe crear funciones genéricas que restrinjan su entrada a colecciones que contienen elementos imprimibles.

func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }

Digamos que tengo un protocolo:

public protocol Printable { typealias T func Print(val:T) }

Y aquí está la implementación

class Printer<T> : Printable { func Print(val: T) { println(val) } }

Mi expectativa era que debía poder usar la variable Printable para imprimir valores como este:

let p:Printable = Printer<Int>() p.Print(67)

El compilador se queja con este error:

"el protocolo ''Imprimible'' solo se puede usar como una restricción genérica porque tiene requisitos de tipo Self o asociados"

Estoy haciendo algo mal ? Cualquier forma de arreglar esto ?

**EDIT :** Adding similar code that works in C# public interface IPrintable<T> { void Print(T val); } public class Printer<T> : IPrintable<T> { public void Print(T val) { Console.WriteLine(val); } } //.... inside Main ..... IPrintable<int> p = new Printer<int>(); p.Print(67)

EDIT 2: ejemplo del mundo real de lo que quiero. Tenga en cuenta que esto no se compilará, pero presenta lo que quiero lograr.

protocol Printable { func Print() } protocol CollectionType<T where T:Printable> : SequenceType { ..... /// here goes implementation ..... } public class Collection<T where T:Printable> : CollectionType<T> { ...... } let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection() for item in col { item.Print() }


Como señala Thomas, puede declarar su variable al no dar un tipo (o puede hacerlo explícitamente como tipo Printer<Int> . Pero aquí hay una explicación de por qué no puede tener un tipo del protocolo Printable .

No puede tratar protocolos con tipos asociados como protocolos regulares y declararlos como tipos de variables independientes. Para pensar por qué, considere este escenario. Suponga que declara un protocolo para almacenar algún tipo arbitrario y luego recuperarlo:

// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"

OK, hasta ahora todo bien.

Ahora, la razón principal por la que un tipo de variable sería un protocolo que un tipo implementa, en lugar del tipo real, es para que pueda asignar diferentes tipos de objetos que se ajusten a ese protocolo a la misma variable, y obtener polimórficos comportamiento en tiempo de ejecución dependiendo de lo que es realmente el objeto.

Pero no puede hacer esto si el protocolo tiene un tipo asociado. ¿Cómo funcionaría el siguiente código en la práctica?

// as you''ve seen this won''t compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()

En el código anterior, ¿cuál sería el tipo de x ? Un Int ? O una String ? En Swift, todos los tipos deben repararse en tiempo de compilación. Una función no puede cambiar dinámicamente de devolver un tipo a otro en función de factores determinados en tiempo de ejecución.

En cambio, solo puede usar StoredType como una restricción genérica. Suponga que desea imprimir cualquier tipo de tipo almacenado. Podrías escribir una función como esta:

func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)

Esto está bien, porque en el momento de la compilación, es como si el compilador escribiera dos versiones de printStoredValue : una para Int s y otra para String s. Dentro de esas dos versiones, se sabe que x es de un tipo específico.


Hay una solución más que no se ha mencionado en esta pregunta, que está utilizando una técnica llamada borrado de tipo . Para lograr una interfaz abstracta para un protocolo genérico, cree una clase o estructura que envuelva un objeto o estructura que se ajuste al protocolo. La clase contenedora, generalmente denominada ''Any {nombre de protocolo}'', se ajusta al protocolo e implementa sus funciones al reenviar todas las llamadas al objeto interno. Pruebe el siguiente ejemplo en un patio de recreo:

import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("/(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5

Se sabe que el tipo de printer es AnyPrinter<Int> y puede usarse para abstraer cualquier posible implementación del protocolo de la Impresora. Si bien AnyPrinter no es técnicamente abstracto, su implementación es solo una caída hacia un tipo de implementación real, y se puede usar para desacoplar los tipos de implementación de los tipos que los usan.

Una cosa a tener en cuenta es que AnyPrinter no tiene que retener explícitamente la instancia base. De hecho, no podemos, ya que no podemos declarar que AnyPrinter tenga una propiedad Printer<T> . En cambio, obtenemos un puntero de función _print a la función de print la base. Al llamar a base.print sin invocarlo, se devuelve una función en la que base es curry como la variable propia y, por lo tanto, se retiene para futuras invocaciones.

Otra cosa a tener en cuenta es que esta solución es esencialmente otra capa de despacho dinámico, lo que significa un ligero impacto en el rendimiento. Además, la instancia de borrado de tipo requiere memoria adicional en la parte superior de la instancia subyacente. Por estas razones, la eliminación de tipos no es una abstracción gratuita.

Obviamente, hay algo de trabajo para configurar el borrado de tipo, pero puede ser muy útil si se necesita la abstracción de protocolo genérico. Este patrón se encuentra en la biblioteca estándar rápida con tipos como AnySequence . Lectura adicional: http://robnapier.net/erasure

PRIMA:

Si decide inyectar la misma implementación de Printer todas partes, puede proporcionar un inicializador conveniente para AnyPrinter que inyecte ese tipo.

extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog

Esta puede ser una forma fácil y SECA de expresar inyecciones de dependencia para protocolos que utiliza en su aplicación.