Je travaillais avec Swinject et un problème me dérange. J'en ai été coincé pendant presque une journée entière. Je soupçonne que cela est dû au Swift étant un langage tapé statiquement mais je ne suis pas tout à fait sûr.
J'ai résumé mon problème dans ce terrain de jeu
protocol Protocol {}
class Class: Protocol {}
let test: Protocol.Type = Class.self
func printType(confromingClassType: Protocol.Type) {
print(confromingClassType)
}
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.self) // "Class"
printType(serviceType: Class.self) // "Class"
print(test) // "Class"
printType(confromingClassType: test) // "Class"
printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'"
J'ai essayé différentes solutions comme test.self ou type (of: test) mais aucune ne fonctionne.
Donc je suppose que je ne peux pas appeler une fonction avec un paramètre générique fourni comme variable?
P.Type
Contre P.Protocol
Il existe deux types de métatypes de protocole. Pour certains protocoles P
et un type conforme C
:
P.Protocol
Décrit le type d'un protocole lui-même (la seule valeur qu'il peut contenir est P.self
).P.Type
Décrit un type concret conforme au protocole. Il peut contenir une valeur de C.self
, Mais pas P.self
Car les protocoles ne sont pas conformes à eux-mêmes (bien qu'une exception à cette règle soit Any
, comme Any
est le top type , donc toute valeur de métatype peut être tapée comme Any.Type
; y compris Any.self
).Le problème auquel vous êtes confronté est que pour un espace réservé générique donné T
, lorsque T
est un protocole P
, T.Type
Est pas P.Type
- c'est P.Protocol
.
Donc, si nous revenons à votre exemple:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
Nous ne pouvons pas passer test
comme argument à printType(serviceType:)
. Pourquoi? Parce que test
est un P.Type
; et il n'y a pas de substitution pour T
qui fait que le paramètre serviceType:
prend un P.Type
.
Si nous substituons dans P
à T
, le paramètre prend un P.Protocol
:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
Si nous substituons dans un type concret pour T
, tel que C
, le paramètre prend un C.Type
:
printType(serviceType: C.self) // C.self is of type C.Type
Bon, nous avons donc appris que si nous pouvons substituer dans un type concret pour T
, nous pouvons passer un C.Type
à la fonction. Peut-on substituer au type dynamique que le P.Type
Encapsule? Malheureusement, cela nécessite une fonctionnalité de langage appelée ouverture d'existentiels , qui n'est actuellement pas directement disponible pour les utilisateurs.
Cependant, Swift ouvre implicitement les existentiels lors de l'accès aux membres sur une instance ou un métatype de type protocole (c'est-à-dire qu'il déterre le type d'exécution et le rend accessible sous la forme d'un espace réservé générique. On peut en tirer parti dans une extension de protocole:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
Il y a pas mal de choses qui se passent ici, alors déballons un peu:
Le membre d'extension callPrintType()
sur P
a un espace réservé générique implicite Self
qui est contraint à P
. Le paramètre implicite self
est saisi à l'aide de cet espace réservé.
Lorsque vous appelez callPrintType()
sur un P.Type
, Swift creuse implicitement le type dynamique que le P.Type
Encapsule (il s'agit de l'ouverture du existentielle) et l'utilise pour satisfaire l'espace réservé Self
. Il transmet ensuite ce métatype dynamique au paramètre implicite self
.
Ainsi, Self
sera satisfait par C
, qui pourra ensuite être transféré sur l'espace réservé générique de printType
T
.
T.Type
N'est pas P.Type
Quand T == P
?Vous remarquerez comment fonctionne la solution de contournement ci-dessus, car nous avons évité de substituer dans P
le paramètre générique T
. Mais pourquoi lors de la substitution dans un type de protocole P
pour T
, T.Type
n'est pas P.Type
?
Eh bien, considérez:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
Et si nous substituions dans P
à T
? Si T.Type
Est P.Type
, Alors ce que nous avons est:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
ce qui est illégal; nous ne pouvons pas affecter P.self
à P.Type
, car il s'agit du type P.Protocol
, et non de P.Type
.
Donc, le résultat est que si vous voulez un paramètre de fonction qui prend un métatype décrivant tout type concret conforme à P
(plutôt que un seul type spécifique de béton conforme) - vous voulez juste un paramètre P.Type
, pas des génériques. Les génériques ne modélisent pas les types hétérogènes, c'est à cela que servent les types de protocoles.
Et c'est exactement ce que vous avez avec printType(conformingClassType:)
:
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
Vous pouvez lui passer test
car il a un paramètre de type P.Type
. Mais vous remarquerez que cela signifie maintenant que nous ne pouvons pas lui passer P.self
, Car il n'est pas de type P.Type
:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
J'ai exécuté votre code dans une cour de récréation, et il semble que ce soit la raison pour laquelle il ne compilera pas
let test: Protocol.Type = Class.self
Si vous supprimez la déclaration de type pour test
, le code fonctionnera et imprimera Class.Type
à la ligne 15
.
Ainsi, le code suivant se compile et s'exécute:
protocol Protocol {}
class Class: Protocol {}
let test = Class.self
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.Type.self) // "Class.Type"
printType(serviceType: Class.Type.self) // "Class.Type"
print(type(of: test)) // "Class.Type"
printType(serviceType: type(of: test)) // "Class.Type"
J'espère que cela résout votre problème.
Modifier
L'erreur que je reçois dans la cour de récréation avec le code d'origine est la suivante:
Playground execution failed: error: Untitled Page 2.xcplaygroundpage:9:1: error: cannot invoke 'printType' with an argument list of type '(serviceType: Protocol.Type.Type)'
printType(serviceType: type(of: test)) // "Class.Type"
Cela signifie que vous appelez Type
2 fois, c'est pourquoi le code ne sera pas compilé, car le type que vous appelez déjà la méthode avec l'argument de type Protocol.Type
.
Si vous modifiez la signature de la méthode comme ceci:
laissez test: Protocol.Type = Class.self
func printType<Service>(serviceType: Service) {
print(serviceType)
}
tout se compilera et fonctionnera correctement, en imprimant Class.type
C'est aussi la raison pour laquelle ma première version de la réponse sera compilée, car elle affectera le bon type pour test
peut appeler .Type
juste une fois.