Swift: test di opzioni per zero


137

Sto usando Xcode 6 Beta 4. Ho questa strana situazione in cui non riesco a capire come testare adeguatamente gli opzionali.

Se ho un xyz opzionale, è il modo corretto di testare:

if (xyz) // Do something

o

if (xyz != nil) // Do something

I documenti dicono di farlo nel primo modo, ma ho scoperto che a volte è necessario il secondo modo e non genera un errore del compilatore, ma altre volte il secondo modo genera un errore del compilatore.

Il mio esempio specifico sta usando il parser XML GData collegato a swift:

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

if (xmlError != nil)

Ecco, se ho appena fatto:

if xmlError

ritornerebbe sempre vero. Tuttavia, se lo faccio:

if (xmlError != nil)

quindi funziona (come funziona in Objective-C).

C'è qualcosa con l'XML di GData e il modo in cui tratta gli optionals che mi mancano?


4
Possiamo vedere un esempio completo per i casi imprevisti e i casi di errore, per favore? (E so che è difficile, ma cerca di iniziare a perdere le parentesi attorno ai condizionali!)
Matt Gibson

1
questo è cambiato in Xcode 6 beta 5
newacct


Ho appena aggiornato la domanda con il caso inaspettato.
tng,

Non ho ancora aggiornato alla beta 5. Lo farò presto; bello da vedere ?? in Swift e comportamento facoltativo più coerente.
tng,

Risposte:


172

In Xcode Beta 5, non ti consentono più di fare:

var xyz : NSString?

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

Questo produce un errore:

non è conforme al protocollo "BooleanType.Protocol"

Devi usare uno di questi moduli:

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

if let xy = xyz {
   // Do something using `xy`.
}

5
Qualcuno può spiegare perché xyz == nil non funziona?
Christoph,

Il secondo esempio dovrebbe essere: // Fai qualcosa usando xy (che è la principale differenza nei casi d'uso tra le due varianti)
Alper,

@Christoph: il messaggio di errore è che la costante 'xyz' veniva usata prima di essere inizializzata. Penso che sia necessario aggiungere '= zero' alla fine della riga della dichiarazione di xyz.
user3207158

Per coloro che sono nuovi a muoversi rapidamente come me e si chiedono: devi ancora scartare xyz all'interno del blocco if. È sicuro, anche se si forzano a scartare
Omar il

@Omar Questa è la bellezza della seconda forma, usi xy all'interno del blocco senza doverlo scartare.
Erik Doernenburg,

24

Per aggiungere alle altre risposte, invece di assegnare a una variabile con un nome diverso all'interno di una ifcondizione:

var a: Int? = 5

if let b = a {
   // do something
}

puoi riutilizzare lo stesso nome di variabile come questo:

var a: Int? = 5

if let a = a {
    // do something
}

Ciò potrebbe aiutarti a evitare di rimanere senza nomi di variabili creative ...

Questo sfrutta l' ombreggiatura variabile supportata in Swift.


18

Uno dei modi più diretti per utilizzare gli optional è il seguente:

Supponendo che xyzsia di tipo opzionale, come Int?ad esempio.

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

In questo modo è possibile verificare se xyzcontiene un valore e, in tal caso, lavorare immediatamente con quel valore.

Per quanto riguarda l'errore del compilatore, il tipo UInt8non è facoltativo (nota no '?') E pertanto non può essere convertito in nil. Assicurati che la variabile con cui stai lavorando sia un optional prima di trattarla come tale.


1
Trovo che questo metodo sia negativo perché creo una nuova variabile (costante) inutilmente, e devo creare un nuovo nome che la maggior parte delle volte sarà peggiore della precedente. Mi ci vuole più tempo per scrivere e per il lettore.
Lombas,

3
È il metodo standard e il metodo consigliato per scartare una variabile opzionale. "'if let" è seriamente potente.
gnasher729,

@ gnasher729 ma cosa succede se si desidera utilizzare solo l'altra parte
Brad Thomas,

15

Swift 3.0, 4.0

Esistono principalmente due modi per verificare facoltativo lo zero. Ecco alcuni esempi con un confronto tra di loro

1. se lasciato

if letè il modo più semplice per controllare facoltativo per zero. Altre condizioni possono essere aggiunte a questo controllo zero, separate da una virgola. La variabile non deve essere nulla per spostarsi alla condizione successiva. Se è richiesto solo un controllo zero, rimuovere le condizioni aggiuntive nel seguente codice.

Oltre a ciò, se xnon è nullo, la chiusura if verrà eseguita e x_valsarà disponibile all'interno. Altrimenti viene attivata la chiusura else.

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

}

2. guard let

guard letpuò fare cose simili. Lo scopo principale è renderlo logicamente più ragionevole. È come dire Assicurati che la variabile non sia nulla, altrimenti interrompi la funzione . guard letpuò anche fare il controllo delle condizioni extra come if let.

Le differenze sono che il valore da scartare sarà disponibile sullo stesso ambito di guard let, come mostrato nel commento qui sotto. Questo porta anche al punto che in chiusura altro, il programma deve uscire dal campo di applicazione corrente, da return, breakecc

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

11

Dalla rapida guida alla programmazione

Se dichiarazioni e involucro forzato

È possibile utilizzare un'istruzione if per scoprire se un'opzione contiene un valore. Se un facoltativo ha un valore, viene valutato come vero; se non ha alcun valore, viene valutato come falso.

Quindi il modo migliore per farlo è

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

e se si sta utilizzando l' xyzistruzione in if. È possibile scartare l' xyzistruzione if in una variabile costante. Quindi non è necessario scartare ogni posto nell'istruzione if in cui xyzviene utilizzato.

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

Questa convenzione è suggerita da applee sarà seguita dai devlopers.


2
In Swift 3 devi farlo if xyz != nil {...}.
psmythirl

In Swift 3 gli opzionali non possono essere usati come booleani. In primo luogo perché è un C-ism che non avrebbe mai dovuto essere nella lingua in primo luogo, e in secondo luogo perché provoca confusione assoluta con Bool opzionale.
gnasher729,

9

Sebbene sia ancora necessario confrontare esplicitamente un facoltativo con nilo utilizzare l'associazione facoltativa per estrarne ulteriormente il valore (ovvero gli opzionali non vengono convertiti implicitamente in valori booleani), vale la pena notare che Swift 2 ha aggiunto l' guardistruzione per aiutare a evitare la piramide del destino quando si lavora con più valori opzionali.

In altre parole, le opzioni includono ora il controllo esplicito di nil:

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

Rilegatura opzionale:

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

E guarddichiarazioni:

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

Nota come l'associazione opzionale ordinaria può portare a un rientro maggiore quando esiste più di un valore opzionale:

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

Puoi evitare questo annidamento con le guardistruzioni:

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

1
come menzionato sopra, puoi farlo anche per sbarazzarti di statmenet opzionali nidificati. if let abc = abc? .xyz? .something {}
Suhaib

1
@Suhaib Ah, vero: puoi anche utilizzare il concatenamento opzionale ( developer.apple.com/library/prerelease/content/documentation/… ) per accedere rapidamente alle proprietà nidificate. Non l'ho menzionato qui perché pensavo che potesse essere troppo tangente rispetto alla domanda originale.
Chris Frederick,

Bella risposta! Ma Swift, OMG ... è ancora molto lontano dall'essere programmatore amichevole!
Turingtest

@turingtested Grazie! In realtà, penso che questo sia un programmatore amichevole nel senso che ti garantisce di non tentare accidentalmente di usare un nilvalore, ma sono d'accordo che la curva di apprendimento è un po 'ripida. :)
Chris Frederick l'

4

Estensione del protocollo Swift 5

Ecco un approccio che utilizza l'estensione del protocollo in modo da poter facilmente incorporare un controllo zero opzionale:

import Foundation

public extension Optional {

    var isNil: Bool {

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

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

uso

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}

Ho ottenuto alcuni voti negativi su questa risposta e vorrei sapere perché. Almeno commenta se hai intenzione di votare.
Brody Robertson

1
È probabilmente l' swiftanalogo di quello pythonistasche cerca di mantenere il linguaggio in una qualche forma di purezza pitonica. La mia opinione? Ho appena inserito il tuo codice nel mio mixin Utils.
javadba,

2

Ora puoi fare rapidamente la seguente cosa che ti permette di riguadagnare un po 'dell'obiettivo-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}

2

Invece if, l'operatore ternario potrebbe tornare utile quando si desidera ottenere un valore in base al fatto che qualcosa sia nullo:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}

xyz == nill'espressione non funziona, ma x != nilfunziona. Non so perché.
Andrew,

2

Un altro approccio oltre a utilizzare ifo guardistruzioni per eseguire l'associazione opzionale è quello di estendere Optionalcon:

extension Optional {

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

}

ifValuericeve una chiusura e la chiama con il valore come argomento quando l'opzione non è nulla. È usato in questo modo:

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

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

helloString = nil

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

Probabilmente dovresti usare un ifo guardcomunque come quelli sono gli approcci più convenzionali (quindi familiari) usati dai programmatori Swift.


2

Un'opzione che non è stata specificatamente coperta è l'utilizzo della sintassi del valore ignorato di Swift:

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

Mi piace dal momento che verificare se si nilsente fuori posto in un linguaggio moderno come Swift. Penso che la ragione per cui sembra fuori posto nilsia fondamentalmente un valore sentinella. Abbiamo eliminato le sentinelle praticamente dappertutto nella programmazione moderna, quindi nilsembra che dovrebbe anche andare.


1

Inoltre puoi usare Nil-Coalescing Operator

Il nil-coalescing operator( a ?? b) scartare un facoltativo ase contiene avalore o restituisce ail valore predefinito bse lo aè nil. L'espressione a è sempre di tipo facoltativo. L'espressione bdeve corrispondere al tipo archiviato all'interno a.

let value = optionalValue ?? defaultValue

In caso optionalValueaffermativo nil, assegna automaticamente valore adefaultValue


Mi piace, perché offre la possibilità di specificare un valore predefinito.
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.")     }

Questo test ha funzionato come previsto in tutti i casi. A proposito, non hai bisogno delle parentesi ().


2
Il caso il PO è preoccupato è: if (xyz != nil).
zaph,

0

Se hai condizioni e desideri scartare e confrontare, che ne dici di sfruttare la valutazione del corto circuito dell'espressione booleana composta come in

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

}

Certo, questo non è leggibile come alcuni degli altri post suggeriti, ma ottiene il lavoro svolto e in qualche modo sintetico rispetto alle altre soluzioni suggerite.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.