Je souhaite créer une classe pouvant stocker des objets conformes à un protocole donné. Les objets doivent être stockés dans un tableau typé. Selon la documentation Swift, les protocoles peuvent être utilisés en tant que types:
Comme il s'agit d'un type, vous pouvez utiliser un protocole dans de nombreux endroits où d'autres types sont autorisés, notamment:
- En tant que type de paramètre ou type de retour dans une fonction, une méthode ou un initialiseur
- En tant que type d'une constante, d'une variable ou d'une propriété
- En tant que type d'éléments dans un tableau, un dictionnaire ou un autre conteneur
Cependant, les erreurs suivantes du compilateur sont générées:
Le protocole 'SomeProtocol' ne peut être utilisé que comme contrainte générique car il a des exigences de type Self ou associé
Comment êtes-vous censé résoudre ceci:
protocol SomeProtocol: Equatable {
func bla()
}
class SomeClass {
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol) {
self.protocols.append(element)
}
func removeElement(element: SomeProtocol) {
if let index = find(self.protocols, element) {
self.protocols.removeAtIndex(index)
}
}
}
Vous avez rencontré une variante d'un problème de protocoles dans Swift pour lequel aucune bonne solution n'existe encore.
Voir aussi Extension du tableau pour vérifier s'il est trié dans Swift? , il contient des suggestions sur la manière de le contourner qui pourrait convenir à votre problème spécifique (votre question est très générique, vous pouvez peut-être trouver une solution de contournement à l'aide de ces réponses).
Vous voulez créer une classe générique, avec une contrainte de type nécessitant que les classes utilisées avec celle-ci soient conformes à SomeProtocol
, comme ceci:
class SomeClass<T: SomeProtocol> {
typealias ElementType = T
var protocols = [ElementType]()
func addElement(element: ElementType) {
self.protocols.append(element)
}
func removeElement(element: ElementType) {
if let index = find(self.protocols, element) {
self.protocols.removeAtIndex(index)
}
}
}
Dans Swift, il existe une classe spéciale de protocoles qui ne fournit pas de polymorphisme par rapport aux types qui l'implémentent. De tels protocoles utilisent des mots-clés Self
ou associatedtype
dans leurs définitions (et Equatable
en est une).
Dans certains cas, il est possible d'utiliser un wrapper effacé pour rendre votre collection homomorphique. Ci-dessous un exemple.
// This protocol doesn't provide polymorphism over the types which implement it.
protocol X: Equatable {
var x: Int { get }
}
// We can't use such protocols as types, only as generic-constraints.
func ==<T: X>(a: T, b: T) -> Bool {
return a.x == b.x
}
// A type-erased wrapper can help overcome this limitation in some cases.
struct AnyX {
private let _x: () -> Int
var x: Int { return _x() }
init<T: X>(_ some: T) {
_x = { some.x }
}
}
// Usage Example
struct XY: X {
var x: Int
var y: Int
}
struct XZ: X {
var x: Int
var z: Int
}
let xy = XY(x: 1, y: 2)
let xz = XZ(x: 3, z: 4)
//let xs = [xy, xz] // error
let xs = [AnyX(xy), AnyX(xz)]
xs.forEach { print($0.x) } // 1 3
La solution limitée que j'ai trouvée consiste à marquer le protocole en tant que protocole de classe uniquement. Cela vous permettra de comparer des objets en utilisant l'opérateur '==='. Je comprends que cela ne fonctionnera pas pour les structures, etc., mais c’était suffisant dans mon cas.
protocol SomeProtocol: class {
func bla()
}
class SomeClass {
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol) {
self.protocols.append(element)
}
func removeElement(element: SomeProtocol) {
for i in 0...protocols.count {
if protocols[i] === element {
protocols.removeAtIndex(i)
return
}
}
}
}
La solution est assez simple:
protocol SomeProtocol {
func bla()
}
class SomeClass {
init() {}
var protocols = [SomeProtocol]()
func addElement<T: SomeProtocol where T: Equatable>(element: T) {
protocols.append(element)
}
func removeElement<T: SomeProtocol where T: Equatable>(element: T) {
protocols = protocols.filter {
if let e = $0 as? T where e == element {
return false
}
return true
}
}
}
Je suppose que votre objectif principal est de conserver une collection d’objets conformes à un protocole, d’ajouter à cette collection et de la supprimer. C’est la fonctionnalité indiquée dans votre client, "SomeClass". Un héritage équitable requiert soi et ce n'est pas nécessaire pour cette fonctionnalité. Nous aurions pu faire fonctionner ce travail dans des tableaux dans Obj-C en utilisant la fonction "index" pouvant prendre un comparateur personnalisé, mais cela n'est pas supporté dans Swift. La solution la plus simple consiste donc à utiliser un dictionnaire au lieu d'un tableau, comme indiqué dans le code ci-dessous. J'ai fourni getElements () qui vous rendra le tableau de protocoles que vous vouliez. Ainsi, quiconque utilisant SomeClass ne saurait même pas qu'un dictionnaire a été utilisé pour la mise en œuvre.
Puisque dans tous les cas, vous auriez besoin de propriétés distinctives pour séparer vos objets, j'ai supposé que c'était "nom". Assurez-vous que votre do element.name = "foo" lorsque vous créez une nouvelle instance SomeProtocol. Si le nom n'est pas défini, vous pouvez toujours créer l'instance, mais celle-ci ne sera pas ajoutée à la collection et addElement () renverra "false".
protocol SomeProtocol {
var name:String? {get set} // Since elements need to distinguished,
//we will assume it is by name in this example.
func bla()
}
class SomeClass {
//var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if
// There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift
/*
static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool {
if (one.name == nil) {return false}
if(toTheOther.name == nil) {return false}
if(one.name == toTheOther.name!) {return true}
return false
}
*/
//The best choice here is to use dictionary
var protocols = [String:SomeProtocol]()
func addElement(element: SomeProtocol) -> Bool {
//self.protocols.append(element)
if let index = element.name {
protocols[index] = element
return true
}
return false
}
func removeElement(element: SomeProtocol) {
//if let index = find(self.protocols, element) { // find not suported in Swift 2.0
if let index = element.name {
protocols.removeValueForKey(index)
}
}
func getElements() -> [SomeProtocol] {
return Array(protocols.values)
}
}
J'ai trouvé une solution Swift pure/pure pas pure sur cet article de blog: http://blog.inferis.org/blog/2015/05/27/Swift-an-array-of-protocols/
L'astuce consiste à se conformer à NSObjectProtocol
car il introduit isEqual()
. Par conséquent, au lieu d'utiliser le protocole Equatable
et son utilisation par défaut de ==
, vous pouvez écrire votre propre fonction pour rechercher l'élément et le supprimer.
Voici l'implémentation de votre fonction find(array, element) -> Int?
:
protocol SomeProtocol: NSObjectProtocol {
}
func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? {
for (index, object) in protocols.enumerated() {
if (object.isEqual(element)) {
return index
}
}
return nil
}
Remarque: Dans ce cas, vos objets conformes à SomeProtocol
doivent hériter de NSObject
.