Disons que j'ai un protocole:
public protocol Printable { typealias T func Print(val:T) }
Et voici la mise en œuvre
class Printer<T> : Printable { func Print(val: T) { println(val) } }
Mon attente était que je devais pouvoir utiliser la variable Printable
pour imprimer des valeurs comme ceci:
let p:Printable = Printer<Int>() p.Print(67)
Le compilateur se plaint de cette erreur:
"protocole 'Imprimable' ne peut être utilisé que comme contrainte générique car il a des exigences de type auto ou associées"
Est-ce que je fais quelque chose de mal ? Aucun moyen de réparer cela ?
**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: Exemple réel de ce que je veux. Notez que ceci ne comstackra pas, mais présente ce que je veux réaliser.
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() }
Comme Thomas le fait remarquer, vous pouvez déclarer votre variable en ne donnant pas du tout de type (ou vous pouvez le donner explicitement comme type Printer<Int>
.) Mais voici une explication de la raison pour laquelle vous ne pouvez pas avoir un type de protocole Printable
.
Vous ne pouvez pas traiter les protocoles avec des types associés comme les protocoles standard et les déclarer comme des types de variables autonomes. Pour réfléchir à pourquoi, considérez ce scénario. Supposons que vous avez déclaré un protocole pour stocker un type arbitraire, puis le récupérer:
// a general protocol that allows for storing and resortingeving // 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 Ssortingngs struct SsortingngStorer: StoringType { typealias Stored = Ssortingng private let _stored: Ssortingng init(_ value: Ssortingng) { _stored = value } func getStored() -> Ssortingng { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let ssortingngStorer = SsortingngStorer("five") ssortingngStorer.getStored() // returns "five"
OK, jusqu'ici tout va bien.
Maintenant, la raison principale pour laquelle vous auriez un type de variable est qu'un protocole implémente un type, plutôt que le type réel, afin que vous puissiez assigner différents types d'objects qui sont tous conforms à ce protocole à la même variable, et polymorphes comportement au moment de l'exécution en fonction de ce que l'object est réellement.
Mais vous ne pouvez pas le faire si le protocole a un type associé. Comment le code suivant fonctionnerait-il dans la pratique?
// as you've seen this won't comstack because // StoringType has an associated type. // randomly assign either a ssortingng or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : ssortingngStorer let x = someStorer.getStored()
Dans le code ci-dessus, quel serait le type de x
? Un Int
? Ou une Ssortingng
? Dans Swift, tous les types doivent être corrigés au moment de la compilation. Une fonction ne peut pas passer dynamicment d'un type à un autre en fonction de facteurs déterminés lors de l'exécution.
Au lieu de cela, vous pouvez uniquement utiliser StoredType
comme contrainte générique. Supposons que vous vouliez imprimer n'importe quel type de type stocké. Vous pourriez écrire une fonction comme celle-ci:
func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(ssortingngStorer)
C'est OK, car au moment de la compilation, c'est comme si le compilateur écrit deux versions de printStoredValue
: une pour Int
s, et une pour Ssortingng
s. Dans ces deux versions, x
est connu pour être d'un type spécifique.
Il y a une solution de plus qui n'a pas été mentionnée sur cette question, qui utilise une technique appelée effacement de type . Pour créer une interface abstraite pour un protocole générique, créez une class ou une structure qui enveloppe un object ou une structure conforme au protocole. La class wrapper, généralement appelée 'Any {nom du protocole}', est elle-même conforme au protocole et implémente ses fonctions en transférant tous les appels à l'object interne. Essayez l'exemple ci-dessous dans une aire de jeux:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U 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
Le type d' printer
est connu pour être AnyPrinter<Int>
et peut être utilisé pour résumer toute implémentation possible du protocole d'imprimante. Bien que AnyPrinter ne soit pas techniquement abstrait, sa mise en œuvre est juste un passage à un type d'implémentation réel, et peut être utilisé pour découpler les types d'implémentation des types qui les utilisent.
Une chose à noter est que AnyPrinter
n'a pas à conserver explicitement l'instance de base. En fait, nous ne pouvons pas car nous ne pouvons pas déclarer AnyPrinter
pour avoir une propriété Printer<T>
. Au lieu de cela, nous obtenons un pointeur de fonction _print
à la fonction d' print
de base. Appeler base.print
sans l'invoquer renvoie une fonction où la base est carrée en tant que variable auto, et est donc conservée pour les invocations futures.
Une autre chose à garder à l'esprit est que cette solution est essentiellement une autre couche de répartition dynamic, ce qui signifie un léger impact sur les performances. En outre, l'instance d'effacement de type requirejs de la memory supplémentaire au-dessus de l'instance sous-jacente. Pour ces raisons, l'effacement de type n'est pas une abstraction gratuite.
Évidemment, il y a du travail pour mettre en place un effacement de type, mais cela peut être très utile si l'abstraction de protocole générique est nécessaire. Ce model se trouve dans la bibliothèque standard rapide avec des types comme AnySequence
. Pour en savoir plus: http://robnapier.net/erasure
PRIME:
Si vous décidez d'injecter la même implémentation de Printer
partout, vous pouvez fournir un initialiseur de commodité pour AnyPrinter
qui injecte ce type.
extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Cela peut être un moyen facile et SEC pour exprimer les injections de dépendance pour les protocoles que vous utilisez dans votre application.
En répondant à votre cas d'utilisation mis à jour:
(btw Printable
est déjà un protocole Swift standard donc vous voudrez probablement choisir un nom différent pour éviter toute confusion)
Pour imposer des ressortingctions spécifiques sur les implémenteurs de protocole, vous pouvez contraindre les typealias du protocole. Donc, pour créer votre collection de protocoles qui nécessite que les éléments soient imprimables:
// 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 }
Maintenant, si vous vouliez implémenter une collection qui ne pouvait contenir que des éléments imprimables:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Cependant, ceci est probablement d'une utilité peu réelle, puisque vous ne pouvez pas contraindre les structures de collection Swift existantes comme cela, seulement celles que vous implémentez.
Au lieu de cela, vous devez créer des fonctions generics qui limitent leur input aux collections contenant des éléments imprimables.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }