web-dev-qa-db-fra.com

Attribuer une valeur à un dictionnaire optionnel dans Swift

Je trouve un comportement surprenant avec des dictionnaires optionnels dans Swift.

var foo:Dictionary<String, String>?

if (foo == nil) {
    foo = ["bar": "baz"]
}
else {
    // Following line errors with "'Dictionary<String, String>?' does
    // not have a member named 'subscript'"
    foo["qux"] = "quux"
}

J'ai beaucoup joué à cela, essayant de comprendre ce qui pourrait me manquer, mais rien ne semble permettre à ce code de fonctionner comme prévu, à moins de rendre le dictionnaire non facultatif. Qu'est-ce que je rate?

Le plus proche que je puisse obtenir est le suivant, mais bien sûr, c'est ridicule.

var foo:Dictionary<String, String>?

if (foo == nil) {
    foo = ["bar": "baz"]
}
else if var foofoo = foo {
    foofoo["qux"] = "quux"
    foo = foofoo
}
10
Garrett Albright

Le moment d'ampoule se produit lorsque vous réalisez qu'un dictionnaire facultatif n'est pas un dictionnaire. Un élément facultatif n'est pas cette chose! C'est une option !! Et c'est tout ce que c'est. Facultatif est lui-même un type. Une option est juste une énumération, englobant les cas possibles nil et une certaine valeur. La valeur encapsulée est un objet complètement différent, stocké à l'intérieur.

Donc, une option n'importe quoi n'agit pas comme le type de cette chose. Ce n'est pas cette chose! C'est juste une option. La seule façon de comprendre la chose est de la déballer.

Il en va de même pour un optionnel implicitement non enveloppé; la différence est simplement que Option implicitement non enveloppé est disposé à produire (exposer) la valeur enveloppée "automatiquement". Mais il est encore, en fait, emballé. Et, comme Bryan Chen l’a observé, il est immuablement enveloppé; l’option facultative ne fait que la garder pour vous - elle ne vous donne pas une place pour jouer avec elle.

31
matt

vous pouvez utiliser ce code

if var foofoo = foo {
    foofoo["qux"] = "quux"
    foo = foofoo
} else {
    foo = ["bar": "baz"]    
}

avec ce code

var foo:Dictionary<String, String>? = Dictionary()
foo[""]=""

error: 'Dictionary<String, String>?' does not have a member named 'subscript'
foo[""]=""
^

le message d'erreur me semble logique que Dictionary<String, String>? n'implémente pas la méthode subscript; vous devez donc le décompresser avant de pouvoir utiliser subscript.

une façon d'appeler la méthode optionnelle est d'utiliser ! c'est-à-dire foo![""], mais ...

var foo:Dictionary<String, String>? = Dictionary()
foo![""]=""

error: could not find member 'subscript'
foo![""]=""
~~~~~~~~^~~

tandis que

var foo:Dictionary<String, String>? = Dictionary()
foo![""]

travaux


il est intéressant que ce code n'ait pas été compilé

var foo:Dictionary<String, String>! = Dictionary() // Implicitly unwrapped optional
foo[""]=""

error: could not find an overload for 'subscript' that accepts the supplied arguments
foo[""]=""
~~~~~~~^~~

var foo:Dictionary<String, String>! = Dictionary() // Implicitly unwrapped optional
foo.updateValue("", forKey: "")

immutable value of type 'Dictionary<String, String>' only has mutating members named 'updateValue'
foo.updateValue("", forKey: "")
^   ~~~~~~~~~~~

le dernier message d'erreur est le plus intéressant, il indique que la variable Dictionary est immuable, de sorte que updateValue(forKey:) (méthode de mutation) ne peut pas être appelé.

donc ce qui s'est passé est probablement que le Optional<> stocke la Dictionary en tant qu'objet immuable (avec let). Donc, même si Optional<> est mutable, vous ne pouvez pas modifier directement l'objet Dictionary sous-jacent (sans réaffecter l'objet Optional)


et ce code fonctionne

class MyDict
{
    var dict:Dictionary<String, String> = [:]

    subscript(s: String) -> String? {
        get {
            return dict[s]
        }
        set {
            dict[s] = newValue
        }
    }
}

var foo:MyDict? = MyDict()
foo!["a"] = "b" // this is how to call subscript of optional object

et cela m'amène à une autre question, pourquoi Array et Dictionary sont-ils du type valeur (struct)? à l'opposé de NSArray et NSDictionary qui sont un type de référence (classe)

7
Bryan Chen

J'ai essayé ceci pour Swift 3.1 et cela a fonctionné: 

if (myDict?[key] = value) == nil {
    myDict = [key: value]
}
0
Merten

C'est parce que votre dictionnaire est optionnel. Si c'est nul, vous n'y ajouterez pas d'entrée.

Vous pouvez faire de cette façon:

var dict: [String : String]?
if let dict = dict {
  dict["key"] = "value" // add a value to an existing dictionary
} else {
  dict = ["key" : "value"] // create a dictionary with this value in it
}

Ou, si un dictionnaire facultatif vous est fourni, par exemple HTTPHeaders - qui, dans AlamoFire, est un dictionnaire [String: String] - et que vous souhaitez soit ajouter une valeur s'il n'est pas nul, soit le créer avec cette valeur s'il est nul, vous pourriez faire comme si:

let headers: HTTPHeaders? // this is an input parameter in a function for example

var customHeaders: HTTPHeaders = headers ?? [:] // nil coalescing
customHeaders["key"] = "value"
0
Frédéric Adda