Swift: pruebas opcionales para cero

137

Estoy usando Xcode 6 Beta 4. Tengo una situación extraña en la que no puedo encontrar la manera de probar adecuadamente las opciones.

Si tengo un xyz opcional, es la forma correcta de probar:

if (xyz) // Do something

o

if (xyz != nil) // Do something

Los documentos dicen que lo haga de la primera manera, pero he descubierto que a veces, la segunda forma es obligatoria y no genera un error del compilador, pero otras veces, la segunda genera un error del compilador.

Mi ejemplo específico es usar el analizador XML GData enlazado a swift:

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

if (xmlError != nil)

Aquí, si acabo de hacer:

if xmlError

siempre volvería cierto. Sin embargo, si lo hago:

if (xmlError != nil)

entonces funciona (como funciona en Objective-C).

¿Hay algo con el XML de GData y la forma en que trata las opciones que me faltan?

tng
fuente
44
¿Podemos ver un ejemplo completo de su caso inesperado y casos de error, por favor? (¡Y sé que es difícil, pero trata de comenzar a perder los corchetes alrededor de los condicionales!)
Matt Gibson
1
esto se cambia en Xcode 6 beta 5
newacct
Acabo de actualizar la pregunta con el caso inesperado.
tng
No he actualizado a beta 5 todavía. Lo haré pronto; encantado de ver ?? en Swift, y un comportamiento opcional más consistente.
tng

Respuestas:

172

En Xcode Beta 5, ya no te permiten hacer:

var xyz : NSString?

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

Esto produce un error:

no se ajusta al protocolo 'BooleanType.Protocol'

Tiene que usar uno de estos formularios:

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

if let xy = xyz {
   // Do something using `xy`.
}
ktzhang
fuente
55
¿Alguien puede explicar por qué xyz == nil no funciona?
Christoph
El segundo ejemplo debería leer: // Haz algo usando xy (que es la principal diferencia en los casos de uso entre las dos variantes)
Alper
@ Christoph: El mensaje de error es esa constante 'xyz' utilizada antes de ser inicializada. Creo que debe agregar '= nil' al final de la línea de declaración de xyz.
user3207158
Para aquellos que son nuevos y rápidos como yo y se preguntan: aún debe desenvolver xyz dentro del bloque if. Sin embargo, es seguro, incluso si fuerza el desenvolvimiento
Omar
@Omar Esa es la belleza de la segunda forma, usas xy dentro del bloque sin tener que desenvolverlo.
Erik Doernenburg el
24

Para agregar a las otras respuestas, en lugar de asignar a una variable con un nombre diferente dentro de una ifcondición:

var a: Int? = 5

if let b = a {
   // do something
}

puede reutilizar el mismo nombre de variable como este:

var a: Int? = 5

if let a = a {
    // do something
}

Esto podría ayudarlo a evitar quedarse sin nombres de variables creativas ...

Esto aprovecha el sombreado variable que es compatible con Swift.

zevij
fuente
18

Una de las formas más directas de usar opcionales es la siguiente:

Suponiendo que xyzes de tipo opcional, como Int?por ejemplo.

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

De esta forma, ambos pueden probar si xyzcontiene un valor y, de ser así, trabajar inmediatamente con ese valor.

Con respecto a su error de compilación, el tipo UInt8no es opcional (nota no '?') Y, por lo tanto, no se puede convertir a nil. Asegúrese de que la variable con la que está trabajando sea opcional antes de tratarla como tal.

Isaac Drachman
fuente
1
Considero que este método es malo porque creo una nueva variable (constante) innecesariamente, y tengo que crear un nuevo nombre que la mayoría de las veces será peor que el anterior. Me lleva más tiempo escribir y para el lector.
Lombas
3
Es el método estándar y el método recomendado para desenvolver una variable opcional. "'si se deja" es muy poderoso.
gnasher729
@ gnasher729, pero ¿y si solo quieres usar la otra parte
Brad Thomas
15

Swift 3.0, 4.0

Hay principalmente dos formas de marcar opcional para nil. Aquí hay ejemplos comparativos entre ellos.

1. si deja

if letes la forma más básica de verificar opcional para nil. Se pueden agregar otras condiciones a esta comprobación nula, separadas por comas. La variable no debe ser nula para moverse para la siguiente condición. Si solo se requiere verificación nula, elimine las condiciones adicionales en el siguiente código.

Aparte de eso, si xno es nulo, el cierre if se ejecutará y x_valestará disponible dentro. De lo contrario, se activa el cierre de lo contrario.

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

}

2. guardia let

guard letpuede hacer cosas similares Su objetivo principal es hacerlo lógicamente más razonable. Es como decir Asegúrese de que la variable no sea nula, de lo contrario, detenga la función . guard letTambién puede hacer una comprobación adicional de la condición como if let.

Las diferencias son que el valor sin envolver estará disponible en el mismo alcance que guard let, como se muestra en el comentario a continuación. Esto también conduce a tal punto que en el cierre de otra cosa, el programa tiene que salir del ámbito actual, por return, break, etc.

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

De la guía de programación rápida

Si declaraciones y desenvolvimiento forzado

Puede usar una instrucción if para averiguar si un opcional contiene un valor. Si un opcional tiene un valor, se evalúa como verdadero; Si no tiene ningún valor, se evalúa como falso.

Entonces la mejor manera de hacer esto es

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

y si está utilizando la xyzinstrucción in if. Entonces puede desenvolver la xyzinstrucción if en variable constante. Por lo tanto, no necesita desenvolver cada lugar en la instrucción if donde xyzse utiliza.

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

Esta convención es sugerida por appley será seguida por los desarrolladores.

codificador
fuente
2
En Swift 3 tienes que hacer if xyz != nil {...}.
psmythirl
En Swift 3, los opcionales no se pueden usar como booleanos. Primero porque es un C-ismo que nunca debería haber estado en el lenguaje en primer lugar, y segundo porque causa una confusión absoluta con Bool opcional.
gnasher729
9

Aunque todavía debe comparar explícitamente un opcional con nilo usar un enlace opcional para extraer adicionalmente su valor (es decir, los opcionales no se convierten implícitamente en valores booleanos), vale la pena señalar que Swift 2 ha agregado la guarddeclaración para ayudar a evitar la pirámide de la fatalidad al trabajar con múltiples valores opcionales.

En otras palabras, sus opciones ahora incluyen verificar explícitamente nil:

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

Encuadernación opcional:

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

Y guarddeclaraciones:

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

Observe cómo el enlace opcional ordinario puede conducir a una mayor sangría cuando hay más de un valor opcional:

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

Puede evitar este anidamiento con guarddeclaraciones:

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
fuente
1
como se mencionó anteriormente, también puede hacer esto para deshacerse de los statmenets opcionales anidados. if let abc = abc? .xyz? .something {}
Suhaib
1
@Suhaib Ah, cierto: también puede usar el encadenamiento opcional ( developer.apple.com/library/prerelease/content/documentation/… ) para acceder rápidamente a las propiedades anidadas. No lo mencioné aquí porque pensé que podría ser una tangente de la pregunta original.
Chris Frederick
¡Gran respuesta! Pero Swift, OMG ... ¡todavía está muy lejos de ser amigable para los programadores!
probado el
@turingtested ¡Gracias! En realidad, creo que esto es amigable para los programadores en el sentido de que garantiza que no intentarás usar un nilvalor accidentalmente , pero estoy de acuerdo en que la curva de aprendizaje es un poco empinada. :)
Chris Frederick
4

Extensión de protocolo Swift 5

Aquí hay un enfoque que usa la extensión de protocolo para que pueda incorporar fácilmente una verificación nula opcional:

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
}
Brody Robertson
fuente
He recibido algunos votos negativos sobre esta respuesta y me gustaría saber por qué. Al menos comente si va a votar en contra.
Brody Robertson
1
Probablemente sea el swiftanálogo del pythonistasque intenta mantener el lenguaje en alguna forma de pureza pitónica. ¿Mi toma? Acabo de levantar su código en mi mezcla de Utils.
javadba
2

Ahora puede hacer rápidamente lo siguiente que le permite recuperar un poco del objetivo-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}
Nicolas Manzini
fuente
2

En lugar de if, el operador ternario puede ser útil cuando desea obtener un valor basado en si algo es nulo:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}
qed
fuente
xyz == nilLa expresión no funciona, pero x != nilfunciona. No se porque.
Andrew
2

Otro enfoque además de usar ifo guarddeclaraciones para hacer el enlace opcional es extender Optionalcon:

extension Optional {

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

}

ifValuerecibe un cierre y lo llama con el valor como argumento cuando el opcional no es nulo. Se usa de esta manera:

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

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

helloString = nil

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

Probablemente debería usar un ifo guardsin embargo, ya que esos son los enfoques más convencionales (por lo tanto familiares) utilizados por los programadores de Swift.

Tiago
fuente
2

Una opción que no se ha cubierto específicamente es usar la sintaxis de valores ignorados de Swift:

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

Me gusta esto, ya que verificar si se nilsiente fuera de lugar en un lenguaje moderno como Swift. Creo que la razón por la que se siente fuera de lugar es que niles básicamente un valor centinela. Hemos eliminado a los centinelas en casi cualquier otro lugar de la programación moderna, por lo que nilparece que también debería irse.

Ben Lachman
fuente
1

También puedes usar Nil-Coalescing Operator

El nil-coalescing operator( a ?? b) desenvuelve un opcional asi contiene avalor, o devuelve ael valor predeterminado bsi aes nil. La expresión a es siempre de tipo opcional. La expresión bdebe coincidir con el tipo que está almacenado dentro a.

let value = optionalValue ?? defaultValue

Si optionalValuees así nil, automáticamente asigna valor adefaultValue

yoAlex5
fuente
Me gusta eso, porque ofrece la opción fácil de especificar un valor predeterminado.
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.")     }

Esta prueba funcionó como se esperaba en todos los casos. Por cierto, no necesita los soportes ().

Mundi
fuente
2
El caso del PO se preocupa es: if (xyz != nil).
zaph
0

Si tiene condiciones y desea desenvolver y comparar, ¿qué tal si aprovecha la evaluación de cortocircuito de la expresión booleana compuesta como en

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

}

Por supuesto, esto no es tan legible como algunas de las otras publicaciones sugeridas, pero hace el trabajo y es algo sucinto que las otras soluciones sugeridas.

RT Denver
fuente