web-dev-qa-db-fra.com

Dans Swift, comment convertir en protocole avec le type associé?

Dans le code suivant, je veux tester si x est une SpecialController. Si c'est le cas, je veux obtenir la currentValue en tant que SpecialValue. Comment est-ce que tu fais ça? Si ce n'est pas avec un casting, alors une autre technique.

La dernière ligne ne sera pas compilée. Il existe une erreur: Le protocole "SpecialController" ne peut être utilisé que comme contrainte générique car il a des exigences de type Self ou associé.

protocol SpecialController {
    associatedtype SpecialValueType : SpecialValue
    var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController {  // does not compile
17
Rob N

Malheureusement, Swift ne prend actuellement pas en charge l’utilisation de protocoles avec les types associés en tant que types réels. Ceci cependant est techniquement possible pour le compilateur; et il pourrait bien être implémenté dans une future version du langage

Dans votre cas, une solution simple consiste à définir un «protocole parallèle» à partir duquel SpecialController dérive et vous permet d'accéder à currentValue via une exigence de protocole qui le type l'efface:

// this assumes SpecialValue doesn't have associated types – if it does, you can repeat
// the same logic by adding TypeErasedSpecialValue, and then using that.
protocol SpecialValue {
  // ...
}

protocol TypeErasedSpecialController {
  var typeErasedCurrentValue: SpecialValue? { get }
}

protocol SpecialController : TypeErasedSpecialController {
  associatedtype SpecialValueType : SpecialValue
  var currentValue: SpecialValueType? { get }
}

extension SpecialController {
  var typeErasedCurrentValue: SpecialValue? { return currentValue }
}

extension String : SpecialValue {}

struct S : SpecialController {
  var currentValue: String?
}

var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController {
  print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
}
11
Hamish

[Modifié pour corriger: : SpecialValue, pas = SpecialValue]

Ce n'est pas possible. SpecialValueController est un "type incomplet" conceptuellement que le compilateur ne peut pas savoir. SpecialValueType, bien qu'il soit contraint par SpecialValue, il n'est pas connu jusqu'à ce qu'il soit déterminé par une classe adoptante. Il s’agit donc d’un espace réservé aux informations insuffisantes. as?- ne peut pas être vérifié. 

Vous pouvez avoir une classe de base qui adopte SpecialController avec un concrete type pour SpecialValueController et plusieurs classes enfant qui héritent de la classe adoptante, si vous recherchez toujours un degré de polymorphisme.

1
BaseZen

Cela ne fonctionne pas car SpecialController n'est pas un type unique. Vous pouvez considérer les types associés comme une sorte de génériques. Un SpecialController avec son SpecialValueType étant un Int est un type complètement différent d'un SpecialController avec son SpecialValueType étant un String, tout comme Optional<Int> est un type complètement différent de Optional<String>.

De ce fait, cela n'a aucun sens de transtyper en SpecialValueType, car cela masquerait le type associé et vous permettrait d'utiliser (par exemple) un SpecialController avec son SpecialValueType étant un Int avec un SpecialController avec son SpecialValueTypeString est attendu.

Comme le suggère le compilateur, SpecialController ne peut être utilisé que comme contrainte générique. Vous pouvez avoir une fonction générique sur T, avec la contrainte que T doit être un SpecialController. Le domaine T couvre désormais tous les types concrets de SpecialController, par exemple un domaine associé à Int et un type String. Pour chaque type associé possible, il existe un SpecialController distinct et, par extension, un T.

Pour approfondir l'analogie Optional<T>. Imaginez si ce que vous essayez de faire était possible. Ce serait un peu comme ceci:

func funcThatExpectsIntOptional(_: Int?) {}

let x: Optional<String> = "An optional string"
// Without its generic type parameter, this is an incomplete type. suppose this were valid
let y = x as! Optional
funcThatExpectsIntOptional(y) // boom.
0
Alexander