Dans Swift, je peux déclarer une constante de type Any
et y mettre une String
.
let any: Any = "hello world"
Bien. D'autre part, je ne peux pas mettre une valeur nil
dans any
car ce n'est pas facultatif.
let any: Any = nil
error: nil cannot initialize specified type 'Any' (aka 'protocol<>')
let any: Any = nil
^
Parfait. Mais pourquoi le compilateur me permet-il d'écrire le code suivant?
let couldBeNil: String? = nil
let any: Any = couldBeNil
print(any) // nil
Any
ne suit-il pas la règle Swift selon laquelle seule une variable optionnelle var/let peut être remplie avec nil
?
Testé avec Xcode Playground 7.2 + Swift 2.1.1
TL; DR; Les options dans Swift sont traduites par le compilateur en instances Optional
enum et, étant donné que Any
peut mapper sur n'importe quelle valeur, il peut être utilisé pour stocker des options.
Comment Swift représente-t-il les optionnels? Cela se fait en mappant SomeType?
à une implémentation concrète de l'énumération Optional
:
Int? => Optional<Int>
String? => Optional<String>
Une déclaration simplifiée de Optional
ressemble à ceci:
enum Optional<T> {
case none // nil
case some(T) // non-nil
}
Désormais, une variable de type Any
peut contenir une valeur enum (ou tout autre type de valeur, ou même d’information de métatype). Elle devrait donc pouvoir contenir par exemple une chaîne nil
, également appelée String?.none
, ou Optional<String>.none
.
Voyons ce qui se passe, cependant. Comme nous le voyons dans la déclaration Optional
, nil
correspond au cas .none
enum pour tous les types:
nil == Optional<String>.none // true
nil == Optional<Int>.none // true
[Double]?.none == nil // also true
Donc, théoriquement, vous devriez pouvoir affecter nil
à une variable déclarée comme Any
. Pourtant, le compilateur ne le permet pas.
Mais pourquoi le compilateur ne vous permet-il pas d'affecter nil
à une variable Any
? C'est parce qu'il ne peut pas déduire à quel type mapper le cas .none
enum. Optional
est une énumération générique. Il a donc besoin de quelque chose pour renseigner le paramètre générique T
et plain nil
est trop large. Quelle valeur .none
doit-il utiliser? Celui de Int
, celui de String
, un autre?
Cela donne un message d'erreur supportant le paragraphe ci-dessus:
let nilAny: Any = nil // error: nil cannot initialize specified type 'Any' (aka 'protocol<>')
Le code suivant fonctionne et équivaut à l'attribution d'une nil
:
let nilAny: Any = Optional<Int>.none
, car la variable Any
ci-dessus contient en réalité une valeur valide de Optional
enum.
Les assignations indirectes fonctionnent aussi, car en coulisse nil
est converti en Optional<Type>.none
.
var nilableBool: Bool? // nilableBool has the Optional<Bool>.none value
var nilBoolAsAny: Any = nilableBool // the compiler has all the needed type information from nilableBool
Contrairement à d'autres langues, Swift nil
correspond à une valeur concrète. Mais il faut un type avec lequel travailler, pour que le compilateur sache quel Optional<T>.none
il doit allouer. Nous pouvons penser que le mot clé fournit la syntaxe sucre.
D'après Swift Docs: "Any
: protocole auquel tous les types sont implicitement conformes."
i.e. typealias Any = protocol<>
Ainsi, lorsque vous déclarez String?
, vous pouvez l’imaginer en tant que Optional<String>
, où Optional<T>
peut être implémenté en tant que:
enum Optional<T> {
case Some(T), None
}
Et les énumérations sont des types, elles sont donc conformes au protocole Any
. Comme indiqué dans les commentaires, nil
n'est pas un type (et n'est donc pas conforme à Any
, c'est l'absence de valeur (et dans ce cas aucune valeur n'a pas de type).
Les options sont cuites dans la langue et ne sont pas des énumérations régulières, mais il est toujours vrai qu'elles se conforment à Any
alors qu'une typeless nil
ne le fait pas.
Pour tous ceux qui cherchent un moyen de vérifier si Any
contient nil
:
Si l'appelant po value
vous indiquera qu'une variable Any
contient la valeur nil
, la ligne suivante ne fonctionne toujours pas:
if value == nil { ... } // Returns false and generates a warning
Vous devez d'abord convertir cette valeur en une valeur nillable, par exemple lorsque Any
contient probablement une Date
:
if Optional<Date>.none == value as? Date { ... } // Returns true and no warning
note: Je ne l'ai pas testé, donc aucune garantie, mais la ligne ci-dessus peut également passer si elle est écrite comme suit:
if Optional<Any>.none == value as? Any { ... }