Swift: test des options pour nul

137

J'utilise Xcode 6 Beta 4. J'ai cette situation étrange où je ne peux pas comprendre comment tester correctement les options.

Si j'ai un xyz facultatif, est la bonne façon de tester:

if (xyz) // Do something

ou

if (xyz != nil) // Do something

Les documents disent de le faire de la première manière, mais j'ai trouvé que parfois, la deuxième méthode est requise et ne génère pas d'erreur de compilation, mais d'autres fois, la deuxième méthode génère une erreur de compilation.

Mon exemple spécifique utilise l'analyseur XML GData couplé à swift:

let xml = GDataXMLDocument(
    XMLString: responseBody,
    options: 0,
    error: &xmlError);

if (xmlError != nil)

Ici, si je viens de le faire:

if xmlError

cela reviendrait toujours vrai. Cependant, si je fais:

if (xmlError != nil)

puis cela fonctionne (comme cela fonctionne en Objective-C).

Y a-t-il quelque chose avec le XML GData et la façon dont il traite les options qui me manquent?

tng
la source
4
Pouvons-nous voir un exemple complet de votre cas inattendu et des cas d'erreur, s'il vous plaît? (Et je sais que c'est difficile, mais essayez de commencer à perdre les parenthèses autour des conditions!)
Matt Gibson
1
cela est changé dans Xcode 6 beta 5
newacct
Je viens de mettre à jour la question avec le cas inattendu.
tng
Je n'ai pas encore mis à jour vers la version bêta 5. Je le ferai bientôt; Jolie à voir ?? dans Swift, et un comportement optionnel plus cohérent.
tng

Réponses:

172

Dans Xcode Beta 5, ils ne vous permettent plus de faire:

var xyz : NSString?

if xyz {
  // Do something using `xyz`.
}

Cela produit une erreur:

n'est pas conforme au protocole 'BooleanType.Protocol'

Vous devez utiliser l'un de ces formulaires:

if xyz != nil {
   // Do something using `xyz`.
}

if let xy = xyz {
   // Do something using `xy`.
}
ktzhang
la source
5
Quelqu'un peut-il expliquer pourquoi xyz == nil ne fonctionne pas?
Christoph
Le deuxième exemple devrait se lire: // Faites quelque chose en utilisant xy (qui est la principale différence dans les cas d'utilisation entre les deux variantes)
Alper
@Christoph: Le message d'erreur est la constante 'xyz' utilisée avant d'être initialisée. Je pense que vous devez ajouter '= nil' à la fin de la ligne de déclaration de xyz.
user3207158
Pour ceux qui sont novices et qui se demandent: vous devez toujours déballer xyz dans le bloc if. C'est sûr cependant, même si vous forcez le déballage
Omar
@Omar C'est la beauté de la deuxième forme, vous utilisez xy à l'intérieur du bloc sans avoir à le déballer.
Erik Doernenburg
24

Pour ajouter aux autres réponses, au lieu d'affecter à une variable nommée différemment à l'intérieur d'une ifcondition:

var a: Int? = 5

if let b = a {
   // do something
}

vous pouvez réutiliser le même nom de variable comme ceci:

var a: Int? = 5

if let a = a {
    // do something
}

Cela peut vous éviter de manquer de noms de variables créatives ...

Cela tire parti de l' ombre variable prise en charge dans Swift.

zevij
la source
18

L'une des façons les plus directes d'utiliser les options est la suivante:

En supposant qu'il xyzest de type facultatif, comme Int?par exemple.

if let possXYZ = xyz {
    // do something with possXYZ (the unwrapped value of xyz)
} else {
    // do something now that we know xyz is .None
}

De cette façon, vous pouvez à la fois tester si xyz contient une valeur et si c'est le cas, travailler immédiatement avec cette valeur.

En ce qui concerne votre erreur de compilation, le type UInt8n'est pas facultatif (notez pas de «?») Et ne peut donc pas être converti en nil. Assurez-vous que la variable avec laquelle vous travaillez est facultative avant de la traiter comme telle.

Isaac Drachman
la source
1
Je trouve cette méthode mauvaise car je crée une nouvelle variable (constante) inutilement, et je dois créer un nouveau nom qui la plupart du temps sera pire que le précédent. Cela me prend plus de temps pour écrire et pour le lecteur.
Lombas
3
C'est la méthode standard et la méthode recommandée pour dérouler une variable facultative. "'if let" est très puissant.
gnasher729
@ gnasher729 mais que faire si vous ne voulez utiliser que la partie else
Brad Thomas
15

Swift 3.0 et 4.0

Il existe principalement deux façons de vérifier facultatif pour nil. Voici des exemples avec comparaison entre eux

1. si loué

if letest le moyen le plus simple de vérifier facultatif pour nil. D'autres conditions peuvent être ajoutées à cette vérification nulle, séparées par une virgule. La variable ne doit pas être nulle pour passer à la condition suivante. Si seule une vérification nulle est requise, supprimez les conditions supplémentaires dans le code suivant.

À part cela, si ce xn'est pas nul, la fermeture if sera exécutée et x_valsera disponible à l'intérieur. Sinon, la fermeture else est déclenchée.

if let x_val = x, x_val > 5 {
    //x_val available on this scope
} else {

}

2. garde laisser

guard letpeut faire des choses similaires. Son objectif principal est de le rendre logiquement plus raisonnable. C'est comme dire Assurez-vous que la variable n'est pas nulle, sinon arrêtez la fonction . guard letpeut également faire une vérification de condition supplémentaire comme if let.

Les différences sont que la valeur non emballée sera disponible sur la même portée que guard let, comme indiqué dans le commentaire ci-dessous. Cela conduit aussi au point que la fermeture d' autre, le programme doit quitter la portée actuelle, par return, breaketc.

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope
Fangming
la source
11

À partir du guide de programmation rapide

Instructions en cas et déballage forcé

Vous pouvez utiliser une instruction if pour savoir si une option contient une valeur. Si un optionnel a une valeur, il prend la valeur true; s'il n'a aucune valeur du tout, il est évalué à faux.

La meilleure façon de procéder est donc

// swift > 3
if xyz != nil {}

et si vous utilisez l' xyzinstruction in if. Vous pouvez ensuite dérouler l' xyzinstruction if dans une variable constante. Ainsi, vous n'avez pas besoin de dérouler chaque endroit de l'instruction if où xyzest utilisé.

if let yourConstant = xyz{
      //use youtConstant you do not need to unwrap `xyz`
}

Cette convention est suggérée par appleet elle sera suivie par les développeurs.

codester
la source
2
Dans Swift 3, vous devez le faire if xyz != nil {...}.
psmythirl
Dans Swift 3, les options ne peuvent pas être utilisées comme booléen. Premièrement parce que c'est un C-isme qui n'aurait jamais dû être dans le langage en premier lieu, et deuxièmement parce qu'il provoque une confusion absolue avec Bool facultatif.
gnasher729
9

Bien que vous deviez toujours comparer explicitement un optionnel avec nilou utiliser une liaison optionnelle pour extraire en plus sa valeur (c'est-à-dire que les options ne sont pas implicitement converties en valeurs booléennes), il convient de noter que Swift 2 a ajouté l' guardinstruction pour éviter la pyramide de malheur lorsque vous travaillez avec plusieurs valeurs facultatives.

En d'autres termes, vos options incluent désormais la vérification explicite de nil:

if xyz != nil {
    // Do something with xyz
}

Reliure facultative:

if let xyz = xyz {
    // Do something with xyz
    // (Note that we can reuse the same variable name)
}

Et guarddéclarations:

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    // e.g. by calling return, break, continue, or throw
    return
}

// Do something with xyz, which is now guaranteed to be non-nil

Notez comment une liaison facultative ordinaire peut entraîner une plus grande indentation lorsqu'il existe plusieurs valeurs facultatives:

if let abc = abc {
    if let xyz = xyz {
        // Do something with abc and xyz
    }        
}

Vous pouvez éviter cette imbrication avec des guardinstructions:

guard let abc = abc else {
    // Handle failure and then exit this code block
    return
}

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    return
}

// Do something with abc and xyz
Chris Frederick
la source
1
comme mentionné ci-dessus, vous pouvez également le faire pour vous débarrasser des statmenets optionnels imbriqués. if let abc = abc? .xyz? .something {}
Suhaib
1
@Suhaib Ah, true: Vous pouvez également utiliser le chaînage facultatif ( developer.apple.com/library/prerelease/content/documentation/… ) pour accéder rapidement aux propriétés imbriquées. Je ne l'ai pas mentionné ici parce que je pensais que c'était peut-être trop tangent par rapport à la question initiale.
Chris Frederick
Très bonne réponse! Mais Swift, OMG ... encore loin d'être convivial pour les programmeurs!
testée le
@turingtested Merci! En fait, je pense que c'est programmeur amical dans le sens où il vous garantit de ne pas tenter d'utiliser accidentellement une nilvaleur, mais je suis d' accord que la courbe d'apprentissage est un peu raide. :)
Chris Frederick
4

Extension du protocole Swift 5

Voici une approche utilisant l'extension de protocole afin que vous puissiez facilement intégrer une vérification facultative de nil:

import Foundation

public extension Optional {

    var isNil: Bool {

        guard case Optional.none = self else {
            return false
        }

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

Usage

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}
Brody Robertson
la source
J'ai reçu quelques votes négatifs sur cette réponse et j'aimerais savoir pourquoi. Au moins commentez si vous allez voter contre.
Brody Robertson
1
C'est probablement l' swiftanalogue du pythonistasqui essaie de garder le langage dans une forme de pureté pythonique. Ma prise? Je viens de mettre votre code dans mon mixin Utils.
javadba
2

Vous pouvez maintenant faire rapidement la chose suivante qui vous permet de retrouver un peu de l'objectif-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}
Nicolas Manzini
la source
2

Au lieu de if, l'opérateur ternaire peut être utile lorsque vous souhaitez obtenir une valeur basée sur le fait que quelque chose est nul:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}
qed
la source
xyz == nill'expression ne fonctionne pas, mais x != nilfonctionne. Je ne sais pas pourquoi.
Andrew
2

Une autre approche en plus d'utiliser des instructions ifou guardpour effectuer la liaison facultative consiste à étendre Optionalavec:

extension Optional {

    func ifValue(_ valueHandler: (Wrapped) -> Void) {
        switch self {
        case .some(let wrapped): valueHandler(wrapped)
        default: break
        }
    }

}

ifValuereçoit une fermeture et l'appelle avec la valeur comme argument lorsque l'option n'est pas nil. Il est utilisé de cette façon:

var helloString: String? = "Hello, World!"

helloString.ifValue {
    print($0) // prints "Hello, World!"
}

helloString = nil

helloString.ifValue {
    print($0) // This code never runs
}

Vous devriez probablement utiliser un ifou guardcependant car ce sont les approches les plus conventionnelles (donc familières) utilisées par les programmeurs Swift.

Tiago
la source
2

Une option qui n'a pas été spécifiquement couverte consiste à utiliser la syntaxe de valeur ignorée de Swift:

if let _ = xyz {
    // something that should only happen if xyz is not nil
}

J'aime ça depuis que la vérification ne nilsemble pas à sa place dans une langue moderne comme Swift. Je pense que la raison pour laquelle il ne semble pas à sa place est qu'il nils'agit essentiellement d'une valeur sentinelle. Nous avons éliminé les sentinelles à peu près partout ailleurs dans la programmation moderne, alors nous avons l' nilimpression que cela devrait aussi aller.

Ben Lachman
la source
1

Vous pouvez également utiliser Nil-Coalescing Operator

Le nil-coalescing operator( a ?? b) déballe un optionnel as'il contient une avaleur, ou renvoie la avaleur par défaut bsi ac'est nil. L'expression a est toujours de type facultatif. L'expression bdoit correspondre au type stocké à l'intérieur a.

let value = optionalValue ?? defaultValue

Si optionalValuec'est le cas nil, il attribue automatiquement une valeur àdefaultValue

yoAlex5
la source
J'aime ça, car cela donne l'option facile de spécifier une valeur par défaut.
thowa
0
var xyz : NSDictionary?

// case 1:
xyz = ["1":"one"]
// case 2: (empty dictionary)
xyz = NSDictionary() 
// case 3: do nothing

if xyz { NSLog("xyz is not nil.") }
else   { NSLog("xyz is nil.")     }

Ce test a fonctionné comme prévu dans tous les cas. BTW, vous n'avez pas besoin des crochets ().

Mundi
la source
2
Le cas de l'OP s'inquiète est: if (xyz != nil).
zaph
0

Si vous avez des conditions conditionnelles et que vous souhaitez dérouler et comparer, que diriez-vous de profiter de l'évaluation de court-circuit de l'expression booléenne composée comme dans

if xyz != nil && xyz! == "some non-nil value" {

}

Certes, ce n'est pas aussi lisible que certains des autres messages suggérés, mais fait le travail et est quelque peu succinct que les autres solutions suggérées.

RT Denver
la source