web-dev-qa-db-fra.com

Quand devrais-je comparer une valeur optionnelle à nil?

Assez souvent, vous devez écrire du code tel que:

if someOptional != nil {
    // do something with the unwrapped someOptional e.g.       
    someFunction(someOptional!)
}

Cela semble un peu prolixe, et j’entends aussi que l’utilisation de l’opérateur ! force de dérouler peut être dangereuse et mieux évitée. Y a-t-il une meilleure façon de gérer cela?

40
Airspeed Velocity

Il est presque toujours inutile de vérifier si une option n'est pas nil. La seule chose que vous devez faire est de savoir si sa nil- ness est la seule chose que vous voulez savoir - vous ne vous souciez pas de la valeur, mais simplement de ce que ce n'est pas nil.

Dans la plupart des autres circonstances, il existe un peu de raccourci Swift qui peut effectuer la tâche avec plus de sécurité et de manière plus concise à l'intérieur de la if pour vous.

Utiliser la valeur si ce n'est pas nil

Au lieu de:

let s = "1"
let i = Int(s)

if i != nil {
    print(i! + 1)
}

vous pouvez utiliser if let:

if let i = Int(s) {
    print(i + 1)
}

Vous pouvez également utiliser var:

if var i = Int(s) {
    print(++i)  // prints 2
}

mais notez que i sera une copie locale - toute modification apportée à i n'affectera pas la valeur dans l'original optionnel.

Vous pouvez décompresser plusieurs options dans un seul if let, et les suivantes peuvent dépendre des précédentes:

if let url = NSURL(string: urlString),
       data = NSData(contentsOfURL: url),
       image = UIImage(data: data)
{
    let view = UIImageView(image: image)
    // etc.
}

Vous pouvez également ajouter des clauses where aux valeurs non enveloppées:

if let url = NSURL(string: urlString) where url.pathExtension == "png",
   let data = NSData(contentsOfURL: url), image = UIImage(data: data)
{ etc. }

Remplacement de nil par une valeur par défaut

Au lieu de:

let j: Int
if i != nil {
    j = i
}
else {
    j = 0
}

ou:

let j = i != nil ? i! : 0

vous pouvez utiliser l'opérateur néo-coalescent, ??:

// j will be the unwrapped value of i,
// or 0 if i is nil
let j = i ?? 0

Associer un optionnel à un non-optionnel

Au lieu de:

if i != nil && i! == 2 {
    print("i is two and not nil")
}

vous pouvez vérifier si les options sont égales aux valeurs non optionnelles:

if i == 2 {
    print("i is two and not nil")
}

Cela fonctionne aussi avec des comparaisons:

if i < 5 { }

nil est toujours égal à nils et est inférieur à toute valeur non -nil.

Faites attention! Il peut y avoir des pièges ici:

let a: Any = "hello"
let b: Any = "goodbye"
if (a as? Double) == (b as? Double) {
    print("these will be equal because both nil...")
}

Appeler une méthode (ou lire une propriété) sur une option

Au lieu de:

let j: Int
if i != nil {
    j = i.successor()
}
else {
   // no reasonable action to take at this point
   fatalError("no idea what to do now...")
}

vous pouvez utiliser le chaînage optionnel, ?.:

let j = i?.successor()

Remarque, j sera désormais également facultatif, afin de prendre en compte le scénario fatalError. Plus tard, vous pourrez utiliser l’une des autres techniques de cette réponse pour gérer l’optionnalité de j, mais vous pourrez souvent différer le déballage de vos options, bien plus tard, voire même pas du tout.

Comme son nom l'indique, vous pouvez les chaîner pour écrire:

let j = s.toInt()?.successor()?.successor()

Le chaînage facultatif fonctionne également avec les indices:

let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]
let sevenOfNine = dictOfArrays["nine"]?[7]  // returns {Some 7}

et fonctions:

let dictOfFuncs: [String:(Int,Int)->Int] = [
      "add":(+),
      "subtract":(-)
]

dictOfFuncs["add"]?(1,1)  // returns {Some 2}

Affectation d'une propriété sur une option

Au lieu de:

if splitViewController != nil {
    splitViewController!.delegate = self 
}

vous pouvez affecter via une chaîne facultative:

splitViewController?.delegate = self

L'affectation aura lieu uniquement si splitViewController est non -nil.

Utiliser la valeur si ce n’est pas nil, ou renflouer (nouveau dans Swift 2.0)

Parfois, dans une fonction, vous voulez écrire un petit morceau de code pour vérifier une option, et si c'est nil, quittez la fonction plus tôt, sinon continuez.

Vous pourriez écrire ceci comme ceci:

func f(s: String) {
    let i = Int(s)
    if i == nil { fatalError("Input must be a number") }
    print(i! + 1)
}

ou pour éviter la force de dérouler, comme ceci:

func f(s: String) {
    if let i = Int(s) {
        print(i! + 1)
    }
    else { 
        fatalErrr("Input must be a number")
    }
}

mais il est beaucoup plus pratique de garder le code de traitement des erreurs au premier plan lors de la vérification. Cela peut également conduire à une nidification désagréable (la "pyramide de Doom").

À la place, vous pouvez utiliser guard, qui ressemble à un if not let:

func f(s: String) {
    guard let i = Int(s)
        else { fatalError("Input must be a number") }

    // i will be an non-optional Int
    print(i+1)
}

La else part must quitte la portée de la valeur gardée, par exemple. return ou fatalError, pour garantir que la valeur gardée sera valable pour le reste de l'étendue.

guard n'est pas limité à la portée de la fonction. Par exemple:

var a = ["0","1","foo","2"]
while !a.isEmpty  {
    guard let i = Int(a.removeLast())
        else { continue }

    print(i+1, appendNewline: false)
}

imprime 321.

Boucle sur des éléments non nuls d'une séquence (nouveauté de Swift 2.0)

Si vous avez une série d'options, vous pouvez utiliser for case let _? pour parcourir tous les éléments non facultatifs:

let a = ["0","1","foo","2"]
for case let i? in a.map({ Int($0)}) {
    print(i+1, appendNewline: false)
}

imprime 321. Ceci utilise la syntaxe de correspondance de modèle pour une option, qui est un nom de variable suivi de ?.

Vous pouvez également utiliser cette correspondance de modèle dans les instructions switch:

func add(i: Int?, _ j: Int?) -> Int? {
    switch (i,j) {
    case (nil,nil), (_?,nil), (nil,_?):
        return nil
    case let (x?,y?):
        return x + y
    }
}

add(1,2)    // 3
add(nil, 1) // nil

Boucle en boucle jusqu'à ce qu'une fonction retourne nil

Tout comme if let, vous pouvez également écrire while let et boucle jusqu'à nil:

while let line = readLine() {
    print(line)
}

Vous pouvez également écrire while var (des mises en garde similaires à if var s'appliquent).

Les clauses where fonctionnent également ici (et terminent la boucle au lieu de sauter):

while let line = readLine() 
where !line.isEmpty {
    print(line)
}

Passage d'un optionnel dans une fonction qui prend un non-optionnel et retourne un résultat

Au lieu de:

let j: Int
if i != nil {
    j = abs(i!)
}
else {
   // no reasonable action to take at this point
   fatalError("no idea what to do now...")
}

vous pouvez utiliser l'opérateur map de optional:

let j = i.map { abs($0) }

Ceci est très similaire au chaînage facultatif, mais pour les cas où vous devez passer la valeur non facultative dans la fonction en tant qu'argument. Comme pour le chaînage facultatif, le résultat sera facultatif.

C'est bien quand vous voulez une option quand même. Par exemple, reduce1 est semblable à reduce, mais utilise la première valeur en tant que valeur de départ, renvoyant une valeur optionnelle si le tableau est vide. Vous pouvez l'écrire comme ceci (en utilisant le mot clé guard de précédemment):

extension Array {
    func reduce1(combine: (T,T)->T)->T? {

        guard let head = self.first
            else { return nil }

        return dropFirst(self).reduce(head, combine: combine)
    }
}

[1,2,3].reduce1(+) // returns 6

Mais au lieu de cela, vous pouvez map la propriété .first et renvoyer ce qui suit:

extension Array {
    func reduce1(combine: (T,T)->T)->T? {
        return self.first.map {
            dropFirst(self).reduce($0, combine: combine)
        }
    }
}

Passage d'une option dans une fonction qui prend une option et renvoie un résultat, en évitant les doubles optionnels gênants

Parfois, vous voulez quelque chose de similaire à map, mais la fonction que vous voulez appeler elle-même renvoie un facultatif. Par exemple:

// an array of arrays
let arr = [[1,2,3],[4,5,6]]
// .first returns an optional of the first element of the array
// (optional because the array could be empty, in which case it's nil)
let fst = arr.first  // fst is now [Int]?, an optional array of ints
// now, if we want to find the index of the value 2, we could use map and find
let idx = fst.map { find($0, 2) }

Mais maintenant, idx est de type Int??, un double-facultatif. À la place, vous pouvez utiliser flatMap, qui "aplatit" le résultat en un seul élément facultatif:

let idx = fst.flatMap { find($0, 2) }
// idx will be of type Int? 
// and not Int?? unlike if `map` was used
106
Airspeed Velocity

Je pense que vous devriez revenir au livre de programmation Swift et apprendre à quoi servent ces choses. ! est utilisé lorsque vous êtes absolument certain que l'option n'est pas nulle. Puisque vous avez déclaré que vous êtes absolument sûr, cela se bloque si vous vous trompez. Ce qui est entièrement intentionnel. Il est "dangereux et préférable d'éviter" dans le sens où votre code est "dangereux et préférable d'éviter". Par exemple: 

if someOptional != nil {
    someFunction(someOptional!)
}

Le ! est absolument sans danger. À moins qu'il y ait une grosse erreur dans votre code, comme écrire par erreur (j'espère que vous repérez le bogue)

if someOptional != nil {
    someFunction(SomeOptional!)
}

dans ce cas, votre application peut se bloquer, vous devez rechercher pourquoi elle se bloque et vous corrigez le bogue, ce qui correspond exactement à la raison du blocage. L’un des objectifs de Swift est qu’il est évident que votre application doit fonctionner correctement, mais comme Swift ne peut pas l’appliquer, cela signifie que votre application fonctionne correctement ou se bloque si possible. Ainsi, les bogues sont supprimés avant que l’application ne soit expédiée. 

2
gnasher729

Vous il y a un moyen. Il s’appelle Chaînage optionnel . De la documentation:

Le chaînage facultatif est un processus permettant d'interroger et d'appeler des propriétés, méthodes, et les indices sur une option qui pourrait actuellement être nil. Si facultatif contient une valeur, la propriété, la méthode ou l’appel d’indice réussit si facultatif est nil, la propriété, la méthode ou l’indice appel retourne nul. Plusieurs requêtes peuvent être chaînées ensemble, et le toute la chaîne échoue gracieusement si l'un des maillons de la chaîne est nul.

Voici quelques exemples

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."

Vous pouvez consulter l'article complet ici .

0
Sergey Pekar

Après de nombreuses réflexions et recherches, j'ai trouvé le moyen le plus simple de déballer une option: 

  • Créez un nouveau fichier Swift et nommez-le UnwrapOperator.Swift

  • Collez le code suivant dans le fichier:

    import Foundation
    import UIKit
    
    protocol OptionalType { init() }
    
    extension String: OptionalType {}
    extension Int: OptionalType {}
    extension Int64: OptionalType {}
    extension Float: OptionalType {}
    extension Double: OptionalType {}
    extension CGFloat: OptionalType {}
    extension Bool: OptionalType {}
    extension UIImage : OptionalType {}
    extension IndexPath : OptionalType {}
    extension NSNumber : OptionalType {}
    extension Date : OptionalType {}
    extension UIViewController : OptionalType {}
    
    postfix operator *?
    postfix func *?<T: OptionalType>( lhs: T?) -> T {
    
        guard let validLhs = lhs else { return T() }
        return validLhs
    }
    
    prefix operator /
    prefix func /<T: OptionalType>( rhs: T?) -> T {
    
        guard let validRhs = rhs else { return T() }
        return validRhs
    }
    
  • Maintenant, le code ci-dessus a créé 2 opérateurs [un préfixe et un préfixe]. 

  • Au moment du déballage, vous pouvez utiliser l’un ou l’autre de ces opérateurs avant ou après les options. 
  • L'explication est simple, les opérateurs retournent la valeur du constructeurs'ils obtiennent nil dans variable sinon la valeur contenue dans la variable.

  • Vous trouverez ci-dessous un exemple d'utilisation: 

    var a_optional : String? = "abc"
    var b_optional : Int? = 123
    
    // before the usage of Operators
    
    print(a_optional) --> Optional("abc")
    print(b_optional) --> Optional(123)
    
    // Prefix Operator Usage
    
    print(/a_optional) --> "abc"
    print(/b_optional) --> 123
    
    // Postfix Operator Usage
    
    print(a_optional*?) --> "abc"
    print(b_optional*?) --> 123
    
  • Voici l'exemple lorsque variable contient nil

    var a_optional : String? = nil
    var b_optional : Int? = nil
    
    // before the usage of Operators
    
    print(a_optional) --> nil
    print(b_optional) --> nil
    
    // Prefix Operator Usage
    
    print(/a_optional) --> ""
    print(/b_optional) --> 0
    
    // Postfix Operator Usage
    
    print(a_optional*?) --> ""
    print(b_optional*?) --> 0
    
  • À vous de choisir l’opérateur que vous utilisez, les deux servent le même objectif.

0
Mr. Bean

Nous pouvons utiliser la liaison facultative.

var x:Int?

if let y = x {
  // x was not nil, and its value is now stored in y
}
else {
  // x was nil
}
0
13th Ghost