Swift: Testoptionen für Null

137

Ich verwende Xcode 6 Beta 4. Ich habe diese seltsame Situation, in der ich nicht herausfinden kann, wie ich angemessen auf Optionen testen kann.

Wenn ich ein optionales xyz habe, ist dies der richtige Weg zum Testen:

if (xyz) // Do something

oder

if (xyz != nil) // Do something

In den Dokumenten wird angegeben, dass dies auf die erste Art und Weise geschehen soll, aber ich habe festgestellt, dass manchmal die zweite Methode erforderlich ist und keinen Compilerfehler generiert. In anderen Fällen generiert die zweite Methode einen Compilerfehler.

Mein spezielles Beispiel ist die Verwendung des GData-XML-Parsers, der schnell überbrückt wird:

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

if (xmlError != nil)

Hier, wenn ich es nur getan hätte:

if xmlError

es würde immer wahr zurückgeben. Wenn ich es jedoch tue:

if (xmlError != nil)

dann funktioniert es (wie es in Objective-C funktioniert).

Gibt es etwas mit GData XML und der Art und Weise, wie Optionen behandelt werden, die mir fehlen?

tng
quelle
4
Können wir bitte ein vollständiges Beispiel für Ihre unerwarteten Fälle und Fehlerfälle sehen? (Und ich weiß, es ist schwer, aber versuchen Sie, die Klammern um die Bedingungen zu verlieren!)
Matt Gibson
1
Dies wird in Xcode 6 Beta 5
Newacct
Ich habe gerade die Frage mit dem unerwarteten Fall aktualisiert.
tng
Ich habe noch nicht auf Beta 5 aktualisiert. Ich werde das bald tun; schön zu sehen ?? in Swift und konsistenteres optionales Verhalten.
tng

Antworten:

172

In Xcode Beta 5 können Sie Folgendes nicht mehr tun:

var xyz : NSString?

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

Dies erzeugt einen Fehler:

entspricht nicht dem Protokoll 'BooleanType.Protocol'

Sie müssen eines dieser Formulare verwenden:

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

if let xy = xyz {
   // Do something using `xy`.
}
ktzhang
quelle
5
Kann jemand erklären, warum xyz == nil nicht funktioniert?
Christoph
Das zweite Beispiel sollte lauten: // Mach etwas mit xy (was der Hauptunterschied in Anwendungsfällen zwischen den beiden Varianten ist)
Alper
@Christoph: Die Fehlermeldung ist die Konstante 'xyz', die vor der Initialisierung verwendet wird. Ich denke, Sie müssen '= nil' am Ende der Deklarationszeile von xyz anhängen.
user3207158
Für diejenigen, die neu in Swift sind und sich fragen: Sie müssen xyz immer noch im if-Block auspacken. Es ist jedoch sicher, auch wenn Sie das Auspacken erzwingen
Omar
@Omar Das ist das Schöne an der zweiten Form. Sie verwenden xy innerhalb des Blocks, ohne es auspacken zu müssen.
Erik Doernenburg
24

So fügen Sie den anderen Antworten hinzu, anstatt einer Variablen mit einem anderen Namen innerhalb einer ifBedingung zuzuweisen:

var a: Int? = 5

if let b = a {
   // do something
}

Sie können denselben Variablennamen wie folgt wiederverwenden:

var a: Int? = 5

if let a = a {
    // do something
}

Auf diese Weise können Sie vermeiden, dass die Namen der kreativen Variablen ausgehen ...

Dies nutzt die in Swift unterstützte variable Schattierung .

zevij
quelle
18

Eine der direktesten Möglichkeiten, Optionen zu verwenden, ist die folgende:

Angenommen, es xyzhandelt sich um einen optionalen Typ, wie Int?zum Beispiel.

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

Auf diese Weise können Sie beide testen, ob xyzein Wert enthalten ist, und in diesem Fall sofort mit diesem Wert arbeiten.

In Bezug auf Ihren Compilerfehler ist der Typ UInt8nicht optional (Anmerkung Nr. '?') Und kann daher nicht in konvertiert werden nil. Stellen Sie sicher, dass die Variable, mit der Sie arbeiten, optional ist, bevor Sie sie wie eine behandeln.

Isaac Drachman
quelle
1
Ich finde diese Methode schlecht, weil ich unnötig eine neue Variable (Konstante) erstelle und einen neuen Namen erstellen muss, der die meiste Zeit schlechter sein wird als der vorherige. Ich brauche mehr Zeit zum Schreiben und für den Leser.
Lombas
3
Dies ist die Standardmethode und die empfohlene Methode zum Entpacken einer optionalen Variablen. "'if let" ist ernsthaft mächtig.
Gnasher729
@ gnasher729 aber was ist, wenn Sie nur den else-Teil verwenden möchten
Brad Thomas
15

Swift 3.0, 4.0

Es gibt hauptsächlich zwei Möglichkeiten, optional auf Null zu prüfen. Hier sind Beispiele mit Vergleich zwischen ihnen

1. wenn lassen

if letist die einfachste Möglichkeit, optional auf Null zu prüfen. An diese Nullprüfung, die durch Komma getrennt ist, können andere Bedingungen angehängt werden. Die Variable darf nicht Null sein, um sich für die nächste Bedingung zu bewegen. Wenn nur keine Prüfung erforderlich ist, entfernen Sie zusätzliche Bedingungen im folgenden Code.

Wenn dies xnicht Null ist, wird der if-Abschluss ausgeführt und x_valist im Inneren verfügbar. Andernfalls wird der else-Abschluss ausgelöst.

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

}

2. Wache lassen

guard letkann ähnliche Dinge tun. Ihr Hauptzweck ist es, es logisch vernünftiger zu machen. Es ist wie zu sagen, stellen Sie sicher, dass die Variable nicht Null ist, sonst stoppen Sie die Funktion . guard letkann auch zusätzliche Zustandsprüfung als durchführenif let .

Die Unterschiede bestehen darin, dass der nicht verpackte Wert im selben Bereich verfügbar guard letist wie im Kommentar unten gezeigt. Dies führt auch zu dem Punkt , dass in anderem Verschluss, das Programm den aktuellen Bereich zu verlassen, durch return, breakusw.

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

Von schnellen Programmierung Führung

If-Anweisungen und erzwungenes Auspacken

Mit einer if-Anweisung können Sie herausfinden, ob ein optionales Element einen Wert enthält. Wenn ein optionales Element einen Wert hat, wird es als wahr ausgewertet. Wenn es überhaupt keinen Wert hat, wird es als falsch ausgewertet.

Der beste Weg, dies zu tun, ist

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

und wenn Sie die xyzin if-Anweisung verwenden. Dann können Sie die xyzif-Anweisung in einer konstanten Variablen auspacken. Sie müssen also nicht jede Stelle in der if-Anweisung auspacken, an der sie xyzverwendet wird.

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

Diese Konvention wird applevon Entwicklern vorgeschlagen und wird von Entwicklern befolgt.

Codester
quelle
2
In Swift 3 müssen Sie tun if xyz != nil {...}.
Psmythirl
In Swift 3 können Optionen nicht als Boolescher Wert verwendet werden. Erstens, weil es sich um einen C-Ismus handelt, der niemals in der Sprache hätte sein dürfen, und zweitens, weil er absolute Verwirrung mit optionalem Bool verursacht.
Gnasher729
9

Obwohl Sie eine Option entweder explizit mit einer optionalen nilBindung vergleichen oder eine optionale Bindung verwenden müssen, um ihren Wert zusätzlich zu extrahieren (dh Optionen werden nicht implizit in boolesche Werte konvertiert), ist es erwähnenswert, dass Swift 2 die guardAnweisung hinzugefügt hat , um die Pyramide des Untergangs beim Arbeiten zu vermeiden mit mehreren optionalen Werten.

Mit anderen Worten, Ihre Optionen umfassen jetzt die explizite Überprüfung auf nil:

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

Optionale Bindung:

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

Und guardAussagen:

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

Beachten Sie, wie eine normale optionale Bindung zu mehr Einrückungen führen kann, wenn mehr als ein optionaler Wert vorhanden ist:

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

Sie können diese Verschachtelung mit guardAnweisungen vermeiden :

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
quelle
1
Wie oben erwähnt, können Sie dies auch tun, um verschachtelte optionale Statmenets zu entfernen. wenn abc = abc? .xyz? .something {}
Suhaib
1
@Suhaib Ah, stimmt: Sie können auch die optionale Verkettung ( developer.apple.com/library/prerelease/content/documentation/… ) verwenden, um schnell auf verschachtelte Eigenschaften zuzugreifen. Ich habe es hier nicht erwähnt, weil ich dachte, es könnte eine zu große Tangente an die ursprüngliche Frage sein.
Chris Frederick
Gute Antwort! Aber Swift, OMG ... noch weit davon entfernt, programmiererfreundlich zu sein!
Turing getestet
@turingtested Danke! Eigentlich denke ich, dass dies programmiererfreundlich ist , da es garantiert, dass Sie nicht versehentlich versuchen, einen nilWert zu verwenden, aber ich stimme zu, dass die Lernkurve etwas steil ist. :)
Chris Frederick
4

Swift 5-Protokollerweiterung

Hier ist ein Ansatz mit Protokollerweiterung, mit dem Sie eine optionale Nullprüfung problemlos einbinden können:

import Foundation

public extension Optional {

    var isNil: Bool {

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

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

Verwendung

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}
Brody Robertson
quelle
Ich habe ein paar Abstimmungen zu dieser Antwort erhalten und würde gerne wissen warum. Zumindest einen Kommentar, wenn Sie abstimmen wollen.
Brody Robertson
1
Es ist wahrscheinlich das swiftAnalogon pythonistasderjenigen, die versuchen, die Sprache in irgendeiner Form von pythonischer Reinheit zu halten. Meine Einstellung? Ich habe gerade Ihren Code in mein Utils-Mixin gehoben.
Javadba
2

Jetzt können Sie schnell Folgendes tun, um ein wenig vom Ziel-c zurückzugewinnen if nil else

if textfieldDate.text?.isEmpty ?? true {

}
Nicolas Manzini
quelle
2

Stattdessen kann der ifternäre Operator nützlich sein, wenn Sie einen Wert erhalten möchten, der darauf basiert, ob etwas Null ist:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}
qed
quelle
xyz == nilAusdruck funktioniert nicht, aber x != nilfunktioniert. Ich weiß nicht warum.
Andrew
2

Ein anderer Ansatz neben der Verwendung von ifoder guardAnweisungen zum Ausführen der optionalen Bindung besteht darin, Folgendes zu erweitern Optional:

extension Optional {

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

}

ifValueempfängt einen Abschluss und ruft ihn mit dem Wert als Argument auf, wenn das optionale nicht null ist. Es wird folgendermaßen verwendet:

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

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

helloString = nil

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

Sie sollten wahrscheinlich einen ifoder guardjedoch verwenden, da dies die konventionellsten (also bekannten) Ansätze sind, die von Swift-Programmierern verwendet werden.

Tiago
quelle
2

Eine Option, die nicht speziell behandelt wurde, ist die Verwendung der ignorierten Wertesyntax von Swift:

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

Ich mag das, da nilin einer modernen Sprache wie Swift nach Gefühlen gesucht wird, die fehl am Platz sind. Ich denke, der Grund, warum es sich fehl am Platz anfühlt, ist, dass niles sich im Grunde genommen um einen Sentinel-Wert handelt. Wir haben Sentinels so ziemlich überall in der modernen Programmierung nilabgeschafft, also sollte es auch so sein.

Ben Lachman
quelle
1

Auch können Sie verwenden Nil-Coalescing Operator

Das nil-coalescing operator( a ?? b) packt ein optionales Element aus, awenn es einen aWert enthält , oder gibt den aStandardwert zurück, bwenn dies der Fall aist nil. Der Ausdruck a ist immer ein optionaler Typ. Der Ausdruck bmuss mit dem Typ übereinstimmen, der darin gespeichert ist a.

let value = optionalValue ?? defaultValue

Wenn dies der Fall optionalValueist nil, wird automatisch ein Wert zugewiesendefaultValue

yoAlex5
quelle
Ich mag das, weil es die einfache Möglichkeit gibt, einen Standardwert anzugeben.
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.")     }

Dieser Test funktionierte in allen Fällen wie erwartet. Übrigens brauchen Sie die Klammern nicht ().

Mundi
quelle
2
Der Fall, um den sich das OP kümmert, ist : if (xyz != nil).
Zaph
0

Wenn Sie eine Bedingung haben und auspacken und vergleichen möchten, können Sie die Kurzschlussbewertung des zusammengesetzten booleschen Ausdrucks wie in nutzen

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

}

Zugegeben, dies ist nicht so lesbar wie einige der anderen vorgeschlagenen Beiträge, aber es erledigt die Arbeit und ist etwas prägnant als die anderen vorgeschlagenen Lösungen.

RT Denver
quelle