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.